From 1ca9473a332fbc11bfa803c03287f964ccdef8e1 Mon Sep 17 00:00:00 2001 From: flakey5 <73616808+flakey5@users.noreply.github.com> Date: Tue, 4 Feb 2025 23:16:28 -0800 Subject: [PATCH] review Signed-off-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- bin/cli.mjs | 4 +- src/generators.mjs | 13 +++- .../utils/checkIndirectReferences.mjs | 3 +- src/generators/ast-js/index.mjs | 10 +-- src/generators/index.mjs | 2 - src/generators/types.d.ts | 4 ++ src/index.mjs | 6 +- src/loaders/javascript.mjs | 33 ++++++++++ src/{loader.mjs => loaders/markdown.mjs} | 26 +------- src/parsers/javascript.mjs | 55 +++++++++++++++++ src/{parser.mjs => parsers/markdown.mjs} | 61 ++----------------- 11 files changed, 120 insertions(+), 97 deletions(-) create mode 100644 src/loaders/javascript.mjs rename src/{loader.mjs => loaders/markdown.mjs} (61%) create mode 100644 src/parsers/javascript.mjs rename src/{parser.mjs => parsers/markdown.mjs} (80%) diff --git a/bin/cli.mjs b/bin/cli.mjs index 012cea9..ba6f8ea 100755 --- a/bin/cli.mjs +++ b/bin/cli.mjs @@ -9,8 +9,8 @@ import { coerce } from 'semver'; import { DOC_NODE_CHANGELOG_URL, DOC_NODE_VERSION } from '../src/constants.mjs'; import createGenerator from '../src/generators.mjs'; import generators from '../src/generators/index.mjs'; -import { createMarkdownLoader } from '../src/loader.mjs'; -import { createMarkdownParser } from '../src/parser.mjs'; +import createMarkdownLoader from '../src/loaders/markdown.mjs'; +import createMarkdownParser from '../src/parsers/markdown.mjs'; import createNodeReleases from '../src/releases.mjs'; const availableGenerators = Object.keys(generators); diff --git a/src/generators.mjs b/src/generators.mjs index ef51628..931f583 100644 --- a/src/generators.mjs +++ b/src/generators.mjs @@ -1,6 +1,14 @@ 'use strict'; -import availableGenerators from './generators/index.mjs'; +import publicGenerators from './generators/index.mjs'; +import astJs from './generators/ast-js/index.mjs'; + +const availableGenerators = { + ...publicGenerators, + // This one is a little special since we don't want it to run unless we need + // it and we also don't want it to be publicly accessible through the CLI. + 'ast-js': astJs, +}; /** * @typedef {{ ast: import('./generators/types.d.ts').GeneratorMetadata}} AstGenerator The AST "generator" is a facade for the AST tree and it isn't really a generator @@ -23,7 +31,7 @@ import availableGenerators from './generators/index.mjs'; * @param {ApiDocMetadataEntry} markdownInput The parsed API doc metadata entries * @param {Array} parsedJsFiles */ -const createGenerator = (markdownInput, jsInput) => { +const createGenerator = markdownInput => { /** * We store all the registered generators to be processed * within a Record, so we can access their results at any time whenever needed @@ -33,7 +41,6 @@ const createGenerator = (markdownInput, jsInput) => { */ const cachedGenerators = { ast: Promise.resolve(markdownInput), - 'ast-js': Promise.resolve(jsInput), }; /** diff --git a/src/generators/api-links/utils/checkIndirectReferences.mjs b/src/generators/api-links/utils/checkIndirectReferences.mjs index d18b10a..982bb11 100644 --- a/src/generators/api-links/utils/checkIndirectReferences.mjs +++ b/src/generators/api-links/utils/checkIndirectReferences.mjs @@ -1,8 +1,7 @@ import { visit } from 'estree-util-visit'; /** - * - * @param program + * @param {import('acorn').Program} program * @param {import('../types.d.ts').ProgramExports} exports * @param {Record} nameToLineNumberMap */ diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs index 8aa1e26..440365a 100644 --- a/src/generators/ast-js/index.mjs +++ b/src/generators/ast-js/index.mjs @@ -1,5 +1,5 @@ -import { createJsLoader } from '../../loader.mjs'; -import { createJsParser } from '../../parser.mjs'; +import createJsLoader from '../../loaders/javascript.mjs'; +import createJsParser from '../../parsers/javascript.mjs'; /** * This generator parses Javascript sources passed into the generator's input @@ -29,12 +29,8 @@ export default { async generate(_, options) { const { loadFiles } = createJsLoader(); - if (!options.input) { - return []; - } - // Load all of the Javascript sources into memory - const sourceFiles = loadFiles(options.input); + const sourceFiles = loadFiles(options.input ?? []); const { parseJsSources } = createJsParser(); diff --git a/src/generators/index.mjs b/src/generators/index.mjs index d2ff094..512271c 100644 --- a/src/generators/index.mjs +++ b/src/generators/index.mjs @@ -8,7 +8,6 @@ import legacyJson from './legacy-json/index.mjs'; import legacyJsonAll from './legacy-json-all/index.mjs'; import addonVerify from './addon-verify/index.mjs'; import apiLinks from './api-links/index.mjs'; -import astJs from './ast-js/index.mjs'; export default { 'json-simple': jsonSimple, @@ -19,5 +18,4 @@ export default { 'legacy-json-all': legacyJsonAll, 'addon-verify': addonVerify, 'api-links': apiLinks, - 'ast-js': astJs, }; diff --git a/src/generators/types.d.ts b/src/generators/types.d.ts index bae5e4d..110b5dc 100644 --- a/src/generators/types.d.ts +++ b/src/generators/types.d.ts @@ -59,6 +59,10 @@ declare global { * * The 'ast' generator is the top-level parser, and if 'ast' is passed to `dependsOn`, then the generator * will be marked as a top-level generator. + * + * The `ast-js` generator is the top-level parser for JavaScript files. It + * passes the ASTs for any JavaScript files given in the input. Like `ast`, + * any generator depending on it is marked as a top-level generator. */ dependsOn: keyof AvailableGenerators | 'ast' | 'ast-js'; diff --git a/src/index.mjs b/src/index.mjs index 4c9cfc9..da9a5fa 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1,8 +1,10 @@ export * as constants from './constants.mjs'; export { default as generators } from './generators/index.mjs'; export { default as createGenerator } from './generators.mjs'; -export * from './loader.mjs'; +export * from './loaders/markdown.mjs'; +export * from './loaders/javascript.mjs'; export { default as createMetadata } from './metadata.mjs'; -export * from './parser.mjs'; +export * from './parsers/markdown.mjs'; +export * from './parsers/javascript.mjs'; export { default as createQueries } from './queries.mjs'; export { default as createNodeReleases } from './releases.mjs'; diff --git a/src/loaders/javascript.mjs b/src/loaders/javascript.mjs new file mode 100644 index 0000000..78fd2c4 --- /dev/null +++ b/src/loaders/javascript.mjs @@ -0,0 +1,33 @@ +'use strict'; + +import { readFile } from 'node:fs/promises'; +import { extname } from 'node:path'; + +import { globSync } from 'glob'; +import { VFile } from 'vfile'; + +/** + * This creates a "loader" for loading Javascript source files into VFiles. + */ +const createLoader = () => { + /** + * Loads the JavaScript source files and transforms them into VFiles + * + * @param {string | Array} searchPath + */ + const loadFiles = searchPath => { + const resolvedFiles = globSync(searchPath).filter( + filePath => extname(filePath) === '.js' + ); + + return resolvedFiles.map(async filePath => { + const fileContents = await readFile(filePath, 'utf-8'); + + return new VFile({ path: filePath, value: fileContents }); + }); + }; + + return { loadFiles }; +}; + +export default createLoader; diff --git a/src/loader.mjs b/src/loaders/markdown.mjs similarity index 61% rename from src/loader.mjs rename to src/loaders/markdown.mjs index f86fbf6..12715e0 100644 --- a/src/loader.mjs +++ b/src/loaders/markdown.mjs @@ -11,7 +11,7 @@ import { VFile } from 'vfile'; * could be used for different things, but here we want to use it to load * Markdown files and transform them into VFiles */ -export const createMarkdownLoader = () => { +const createLoader = () => { /** * Loads API Doc files and transforms it into VFiles * @@ -36,26 +36,4 @@ export const createMarkdownLoader = () => { return { loadFiles }; }; -/** - * This creates a "loader" for loading Javascript source files into VFiles. - */ -export const createJsLoader = () => { - /** - * Loads the JavaScript source files and transforms them into VFiles - * - * @param {string | Array} searchPath - */ - const loadFiles = searchPath => { - const resolvedFiles = globSync(searchPath).filter( - filePath => extname(filePath) === '.js' - ); - - return resolvedFiles.map(async filePath => { - const fileContents = await readFile(filePath, 'utf-8'); - - return new VFile({ path: filePath, value: fileContents }); - }); - }; - - return { loadFiles }; -}; +export default createLoader; diff --git a/src/parsers/javascript.mjs b/src/parsers/javascript.mjs new file mode 100644 index 0000000..e68e9d9 --- /dev/null +++ b/src/parsers/javascript.mjs @@ -0,0 +1,55 @@ +'use strict'; + +import * as acorn from 'acorn'; + +/** + * Creates a Javascript source parser for a given source file + */ +const createParser = () => { + /** + * Parses a given JavaScript file into an ESTree AST representation of it + * + * @param {import('vfile').VFile | Promise} sourceFile + * @returns {Promise} + */ + const parseJsSource = async sourceFile => { + // 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 resolvedSourceFile = await Promise.resolve(sourceFile); + + if (typeof resolvedSourceFile.value !== 'string') { + throw new TypeError( + `expected resolvedSourceFile.value to be string but got ${typeof resolvedSourceFile.value}` + ); + } + + const res = acorn.parse(resolvedSourceFile.value, { + allowReturnOutsideFunction: true, + ecmaVersion: 'latest', + locations: true, + }); + + return { + ...res, + path: resolvedSourceFile.path, + }; + }; + + /** + * Parses multiple JavaScript files into ESTree ASTs by wrapping parseJsSource + * + * @param {Array>} apiDocs List of API doc files to be parsed + * @returns {Promise>} + */ + const parseJsSources = async apiDocs => { + // We do a Promise.all, to ensure that each API doc is resolved asynchronously + // but all need to be resolved first before we return the result to the caller + const resolvedApiDocEntries = await Promise.all(apiDocs.map(parseJsSource)); + + return resolvedApiDocEntries; + }; + + return { parseJsSource, parseJsSources }; +}; + +export default createParser; diff --git a/src/parser.mjs b/src/parsers/markdown.mjs similarity index 80% rename from src/parser.mjs rename to src/parsers/markdown.mjs index aba84b8..65707e3 100644 --- a/src/parser.mjs +++ b/src/parsers/markdown.mjs @@ -5,18 +5,17 @@ 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 * as acorn from 'acorn'; -import createMetadata from './metadata.mjs'; -import createQueries from './queries.mjs'; +import createMetadata from '../metadata.mjs'; +import createQueries from '../queries.mjs'; -import { getRemark } from './utils/remark.mjs'; -import { createNodeSlugger } from './utils/slugger.mjs'; +import { getRemark } from '../utils/remark.mjs'; +import { createNodeSlugger } from '../utils/slugger.mjs'; /** * Creates an API doc parser for a given Markdown API doc file */ -export const createMarkdownParser = () => { +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 = getRemark(); @@ -186,52 +185,4 @@ export const createMarkdownParser = () => { return { parseApiDocs, parseApiDoc }; }; -/** - * Creates a Javascript source parser for a given source file - */ -export const createJsParser = () => { - /** - * Parses a given JavaScript file into an ESTree AST representation of it - * - * @param {import('vfile').VFile | Promise} sourceFile - * @returns {Promise} - */ - const parseJsSource = async sourceFile => { - // 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 resolvedSourceFile = await Promise.resolve(sourceFile); - - if (typeof resolvedSourceFile.value !== 'string') { - throw new TypeError( - `expected resolvedSourceFile.value to be string but got ${typeof resolvedSourceFile.value}` - ); - } - - const res = acorn.parse(resolvedSourceFile.value, { - allowReturnOutsideFunction: true, - ecmaVersion: 'latest', - locations: true, - }); - - return { - ...res, - path: resolvedSourceFile.path, - }; - }; - - /** - * Parses multiple JavaScript files into ESTree ASTs by wrapping parseJsSource - * - * @param {Array>} apiDocs List of API doc files to be parsed - * @returns {Promise>} - */ - const parseJsSources = async apiDocs => { - // We do a Promise.all, to ensure that each API doc is resolved asynchronously - // but all need to be resolved first before we return the result to the caller - const resolvedApiDocEntries = await Promise.all(apiDocs.map(parseJsSource)); - - return resolvedApiDocEntries; - }; - - return { parseJsSource, parseJsSources }; -}; +export default createParser;