Skip to content

refactor: applied bug fixes and code refactor from #55 #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import eslintConfigPrettier from 'eslint-config-prettier';
import globals from 'globals';

export default [
{ languageOptions: { globals: globals.node } },
// Visit https://eslint.org/docs/latest/rules to learn more about these rules
// @see https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores
{
files: ['src/**/*.mjs'],
languageOptions: { globals: globals.node },
},
// @see https://eslint.org/docs/latest/rules to learn more about these rules
pluginJs.configs.recommended,
eslintConfigPrettier,
];
19 changes: 10 additions & 9 deletions src/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,6 @@ export const DOC_MDN_BASE_URL_JS_PRIMITIVES = `${DOC_MDN_BASE_URL_JS}Data_struct
// This is the base URL for the MDN JavaScript global objects documentation
export const DOC_MDN_BASE_URL_JS_GLOBALS = `${DOC_MDN_BASE_URL_JS}Reference/Global_Objects/`;

// These are YAML keys from the Markdown YAML Metadata that should always be arrays
export const DOC_API_YAML_KEYS_ARRAYS = [
'added',
'napiVersion',
'deprecated',
'removed',
'introduced_in',
];

// These are YAML keys from the Markdown YAML metadata that should be
// removed and appended to the `update` key
export const DOC_API_YAML_KEYS_UPDATE = [
Expand Down Expand Up @@ -73,6 +64,16 @@ export const DOC_API_HEADING_TYPES = [
},
];

// This is a mapping for the `API` updates within the Markdown content and their respective
// content that should be mapping into `changes` property for better mapping on HTML
export const DOC_API_UPDATE_MAPPING = {
added: 'Added in',
removed: 'Removed in',
deprecated: 'Deprecated since',
introduced_in: 'Introduced in',
napiVersion: 'N-API Version',
};

// This is a mapping for types within the Markdown content and their respective
// JavaScript primitive types within the MDN JavaScript docs
// @see DOC_MDN_BASE_URL_JS_PRIMITIVES
Expand Down
1 change: 0 additions & 1 deletion src/generators.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import availableGenerators from './generators/index.mjs';

/**
* @typedef {import('./types.d.ts').ApiDocMetadataEntry} ApiDocMetadataEntry Local type alias for the API doc metadata entry
* @typedef {{ ast: import('./generators/types.d.ts').GeneratorMetadata<ApiDocMetadataEntry, ApiDocMetadataEntry>}} AstGenerator The AST "generator" is a facade for the AST tree and it isn't really a generator
* @typedef {import('./generators/types.d.ts').AvailableGenerators & AstGenerator} AllGenerators A complete set of the available generators, including the AST one
*
Expand Down
3 changes: 1 addition & 2 deletions src/generators/json-simple/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { join } from 'node:path';
* This generator is a top-level generator, and it takes the raw AST tree of the API doc files
* and returns a stringified JSON version of the API docs.
*
* @typedef {import('../../types.d.ts').ApiDocMetadataEntry[]} Input
* @typedef {Array<ApiDocMetadataEntry>} Input
*
* @type {import('../types.d.ts').GeneratorMetadata<Input, string>}
*/
Expand All @@ -25,7 +25,6 @@ export default {
dependsOn: 'ast',

async generate(input, options) {

// This simply grabs all the different files and stringifies them
const stringifiedContent = JSON.stringify(input, null, 2);

Expand Down
2 changes: 1 addition & 1 deletion src/generators/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SemVer } from 'semver';
import type availableGenerators from './index.mjs';
import { ApiDocReleaseEntry } from '../types';
import type { ApiDocReleaseEntry } from '../types';

// All available generators as an inferable type, to allow Generator interfaces
// to be type complete and runtime friendly within `runGenerators`
Expand Down
2 changes: 1 addition & 1 deletion src/loader.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

import { extname } from 'node:path';
import { readFile } from 'node:fs/promises';
import { extname } from 'node:path';

import { globSync } from 'glob';
import { VFile } from 'vfile';
Expand Down
80 changes: 64 additions & 16 deletions src/metadata.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
'use strict';

import { u as createTree } from 'unist-builder';

import { compare } from 'semver';

import { DOC_API_UPDATE_MAPPING } from './constants.mjs';
import { coerceSemVer } from './utils/generators.mjs';

/**
* @typedef {import('./types.d.ts').ApiDocMetadataEntry} ApiDocMetadataEntry Local type alias for the API doc metadata entry
* @typedef {import('./types.d.ts').ApiDocRawMetadataEntry} ApiDocRawMetadataEntry Local type alias for the API doc raw metadata entry
* @typedef {import('./types.d.ts').HeadingMetadataEntry} HeadingMetadataEntry Local type alias for the heading metadata entry
*
* This method allows us to handle creation of Metadata entries
* within the current scope of API docs being parsed
*
Expand All @@ -14,6 +17,29 @@
* @param {InstanceType<typeof import('github-slugger').default>} slugger A GitHub Slugger
*/
const createMetadata = slugger => {
/**
* Maps `updates` into `changes` format, merges them and sorts them by version
*
* @param {Array<ApiDocMetadataUpdate>} updates Original updates to be merged
* @param {Array<ApiDocMetadataChange>} changes Changes to be merged into updates
* @returns {Array<ApiDocMetadataChange>} Mapped, merged and sorted changes
*/
const mergeUpdatesIntoChanges = (updates, changes) => {
// Maps the `updates` array into the same format used by the `changes` array
// So that for generators such as HTML, we render all the changes + updates
// into one single list of changes, for example a HTML table
const mappedUpdatesIntoChanges = updates.map(({ version, type }) => ({
version,
'pr-url': undefined,
description: `${DOC_API_UPDATE_MAPPING[type]}: ${version.join(', ')}`,
}));

// Sorts the updates and changes by the first version on a given entry
return [...mappedUpdatesIntoChanges, ...changes].sort((a, b) =>
compare(coerceSemVer(a.version[0]), coerceSemVer(b.version[0]))
);
};

/**
* This holds a temporary buffer of raw metadata before being
* transformed into NavigationEntries and MetadataEntries
Expand All @@ -31,8 +57,13 @@ const createMetadata = slugger => {
name: undefined,
depth: -1,
},
properties: {},
stability: undefined,
properties: {
type: undefined,
source_link: undefined,
updates: [],
changes: [],
},
stability: createTree('root', []),
};

return {
Expand All @@ -47,10 +78,10 @@ const createMetadata = slugger => {
/**
* Set the Stability Index of a given Metadata
*
* @param {ApiDocMetadataEntry['stability']} stability The new stability metadata
* @param {import('unist').Parent} stability The stability index node to be added
*/
setStability: stability => {
internalMetadata.stability = stability;
addStability: stability => {
internalMetadata.stability.children.push(stability);
},
/**
* Set the Metadata (from YAML if exists) properties to the current Metadata entry
Expand All @@ -61,13 +92,27 @@ const createMetadata = slugger => {
* meaning that this method can be called multiple times to update the properties
* and complement each set of data.
*
* Note: This ensures only valid properties get defined and that we don't accidentally override
* values, when we just want to complement them whenever possible.
*
* @param {Partial<ApiDocRawMetadataEntry>} properties Extra Metadata properties to be defined
*/
updateProperties: properties => {
internalMetadata.properties = {
...internalMetadata.properties,
...properties,
};
if (properties.type) {
internalMetadata.properties.type = properties.type;
}

if (properties.source_link) {
internalMetadata.properties.source_link = properties.source_link;
}

if (properties.changes) {
internalMetadata.properties.changes.push(...properties.changes);
}

if (properties.updates) {
internalMetadata.properties.updates.push(...properties.updates);
}
},
/**
* Generates a new Navigation entry and pushes them to the internal collection
Expand All @@ -90,7 +135,6 @@ const createMetadata = slugger => {

const {
type: yamlType,
name: yamlName,
source_link: sourceLink,
updates = [],
changes = [],
Expand All @@ -99,18 +143,22 @@ const createMetadata = slugger => {
// We override the type of the heading if we have a YAML type
internalMetadata.heading.type = yamlType || internalMetadata.heading.type;

// Maps the Stability Index AST nodes into a JSON objects from their data properties
internalMetadata.stability.toJSON = () =>
internalMetadata.stability.children.map(node => node.data);

// Returns the Metadata entry for the API doc
return {
// The API file basename (without the extension)
api: yamlName || apiDoc.stem,
api: apiDoc.stem,
// The path/slug of the API section
slug: `${apiDoc.stem}.html${slugHash}`,
// The source link of said API section
sourceLink: sourceLink,
// The latest updates to an API section
updates,
// The full-changeset to an API section
changes,
changes: mergeUpdatesIntoChanges(updates, changes),
// The Heading metadata
heading: internalMetadata.heading,
// The Stability Index of the API section
Expand Down
36 changes: 18 additions & 18 deletions src/parser.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
'use strict';

import { remark } from 'remark';
import remarkGfm from 'remark-gfm';

import { u as createTree } from 'unist-builder';
import { findAfter } from 'unist-util-find-after';
import { remove } from 'unist-util-remove';
import { selectAll } from 'unist-util-select';
import { SKIP, visit } from 'unist-util-visit';
import { findAfter } from 'unist-util-find-after';

import createMetadata from './metadata.mjs';
import createQueries from './queries.mjs';

import { transformTypeToReferenceLink } from './utils/parser.mjs';
import { getRemark } from './utils/remark.mjs';
import { createNodeSlugger } from './utils/slugger.mjs';

/**
Expand All @@ -20,11 +19,10 @@ import { createNodeSlugger } from './utils/slugger.mjs';
const createParser = () => {
// Creates an instance of the Remark processor with GFM support
// which is used for stringifying the AST tree back to Markdown
const remarkProcessor = remark().use(remarkGfm);
const remarkProcessor = getRemark();

const {
updateLinkReference,
updateTypeToReferenceLink,
updateMarkdownLink,
addYAMLMetadata,
addHeadingMetadata,
Expand All @@ -45,14 +43,24 @@ const createParser = () => {
* Then once we have the whole file parsed, we can split the resulting string into sections
* and seal the Metadata Entries (`.create()`) and return the result to the caller of parae.
*
* @type {Array<import('./types.d.ts').ApiDocMetadataEntry>}
* @type {Array<ApiDocMetadataEntry>}
*/
const metadataCollection = [];

// We allow the API doc VFile to be a Promise of a VFile also,
// hence we want to ensure that it first resolves before we pass it to the parser
const resolvedApiDoc = await Promise.resolve(apiDoc);

// Normalizes all the types in the API doc file to be reference links
// which needs to be done before the actual processing is done
// since we're replacing raw text within the Markdown
// @TODO: This could be moved to another place responsible for handling
// text substitutions at the beginning of the parsing process (as dependencies)
resolvedApiDoc.value = String(resolvedApiDoc.value).replaceAll(
createQueries.QUERIES.normalizeTypes,
transformTypeToReferenceLink
);

// Creates a new Slugger instance for the current API doc file
const nodeSlugger = createNodeSlugger();

Expand All @@ -73,14 +81,6 @@ const createParser = () => {
// anymore, since all link references got updated to be plain links
remove(apiDocTree, markdownDefinitions);

// Handles API type references transformation into links that point
// to the reference to a said API type
visit(apiDocTree, createQueries.UNIST.isTextWithType, node => {
updateTypeToReferenceLink(node);

return SKIP;
});

// Handles normalisation URLs that reference to API doc files with .md extension
// to replace the .md into .html, since the API doc files get eventually compiled as HTML
visit(apiDocTree, createQueries.UNIST.isMarkdownUrl, node => {
Expand Down Expand Up @@ -116,7 +116,7 @@ const createParser = () => {
// the document itself.
const stop =
headingNode === nextHeadingNode
? apiDocTree.children.length - 1
? apiDocTree.children.length
: apiDocTree.children.indexOf(nextHeadingNode);

// Retrieves all the Nodes that should belong to the current API doc section
Expand All @@ -130,10 +130,10 @@ const createParser = () => {
// and then apply the Stability Index Metadata to the current Metadata entry
visit(apiSectionTree, createQueries.UNIST.isStabilityIndex, node => {
// Retrieves the subtree of the Stability Index Node
const stabilityNodes = createTree('root', node.children[0].children);
const stabilityNode = createTree('root', node.children);

// Adds the Stability Index Metadata to the current Metadata entry
addStabilityIndexMetadata(stabilityNodes, apiEntryMetadata);
addStabilityIndexMetadata(stabilityNode, apiEntryMetadata);

return SKIP;
});
Expand Down
Loading
Loading