From 230825ddf3a972c1473e0f5ad627b58cd26a26b4 Mon Sep 17 00:00:00 2001 From: Joshua T Kalis Date: Thu, 21 Nov 2024 12:21:42 -0500 Subject: [PATCH] broke modules out --- scripts/docblock.mjs | 155 ++++++++++---------------------------- scripts/file-details.mjs | 80 ++++++++++++++++++++ scripts/pragma-parser.mjs | 50 ++++++++++++ 3 files changed, 168 insertions(+), 117 deletions(-) create mode 100644 scripts/file-details.mjs create mode 100644 scripts/pragma-parser.mjs diff --git a/scripts/docblock.mjs b/scripts/docblock.mjs index f0a4ccb6..b1a55166 100644 --- a/scripts/docblock.mjs +++ b/scripts/docblock.mjs @@ -12,125 +12,46 @@ * governing permissions and limitations under the License. */ -import { parseFileSync } from '@swc/core'; import { parse } from 'comment-parser'; -import { fs, glob } from 'zx'; - -const SWC_OPTIONS = { - syntax: 'typescript', - target: 'es2022', -}; - -function exportsReducer(acc, node) { - if (node.type === 'ExportDeclaration') { - if (node.declaration.identifier) { - acc.push(node.declaration.identifier.value); - } else if (node.declaration.declarations) { - for (const inner of node.declaration.declarations) { - acc.push(inner.id.value); - } - } +import { glob } from 'zx'; + +import { getFileDetails } from './file-details.mjs'; + +const noDocblock = ( + await glob(['**/*.{js,ts,tsx,mjs}'], { + ignore: [ + '**/.github/**', + '**/apps/**', + '**/__fixtures__/**', + '**/__mock__/**', + '**/coverage/**', + '**/dist/**', + '**/node_modules/**', + '**/tooling/**', + '**/*.test*', + '**/*.config*', + '**/*.css*', + '**/*.stories*', + ], + }) +).filter((file) => { + try { + const [source, exports] = getFileDetails(file); + + // has exports and no docblock + return exports.length && !parse(source).length; + } catch (_) { + // ignore non-parsing files + return false; } +}); - return acc; -} - -function getFileDetails(path) { - const ast = parseFileSync(path, SWC_OPTIONS); - const source = fs.readFileSync(path, 'utf8'); - - const exports = ast.body.reduce(exportsReducer, []); - const [pragma, ...list] = pragmaParser(source); - - const result = [source]; - - switch (pragma) { - case 'ignore': - result.push( - list[0] === '*' ? [] : exports.filter((name) => !list.includes(name)), - ); - break; - case 'only': - result.push(exports.filter((name) => list.includes(name))); - break; - default: - result.push(exports); - break; - } - - return result; -} - -const pragmaParser = (() => { - const rCleaner = /,\s*/; - const rPragma = /\/\/\s*@export-(ignore|only)(?:\s*\[([^\]]+)\])?/; - const rPrivate = /\/\/\s*__private-exports/i; +if (noDocblock.length) { + console.error( + `${noDocblock.length} files missing a docblock:`, + JSON.stringify(noDocblock, null, 4), + ); - return (src) => { - if (src.match(rPrivate)?.[0]) { - return ['ignore', '*']; - } - - const found = src.match(rPragma); - - if (!found) { - return []; - } - - const pragmas = found - .slice(1) - .reduce((a, b = '*') => [ - a, - ...(b ? b.replace(rCleaner, ',').split(',') : b), - ]); - - if (pragmas[0] === 'only' && pragmas[1] === '*') { - return []; - } - - return pragmas; - }; -})(); - -async function run() { - const noDocblock = ( - await glob(['**/*.{js,ts,tsx,mjs}'], { - ignore: [ - '**/.github/**', - '**/apps/**', - '**/__fixtures__/**', - '**/__mock__/**', - '**/coverage/**', - '**/dist/**', - '**/node_modules/**', - '**/tooling/**', - '**/*.test*', - '**/*.config*', - '**/*.css*', - '**/*.stories*', - ], - }) - ).filter((file) => { - try { - const [source, exports] = getFileDetails(file); - - // has exports and no docblock - return exports.length && !parse(source).length; - } catch (_) { - // ignore non-parsing files - return false; - } - }); - - if (noDocblock.length) { - console.error( - `${noDocblock.length} files missing a docblock:`, - JSON.stringify(noDocblock, null, 4), - ); - - // TODO: enable error-ing once all file are complying - // process.exit(1); - } + // TODO: enable error-ing once all file are complying + // process.exit(1); } - -await run(); diff --git a/scripts/file-details.mjs b/scripts/file-details.mjs new file mode 100644 index 00000000..c9a34cbd --- /dev/null +++ b/scripts/file-details.mjs @@ -0,0 +1,80 @@ +/* + * Copyright 2024 Hypergiant Galactic Systems Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { parseFileSync } from '@swc/core'; +import { fs } from 'zx'; + +import { pragmaParser } from './pragma-parser.mjs'; + +const SWC_OPTIONS = { + syntax: 'typescript', + target: 'es2022', +}; + +/** Collect the exported members' names. */ +function exportsReducer(acc, member) { + if (member.type === 'ExportDeclaration') { + if (member.declaration.declarations) { + // const, let, var allow for comma separated values + for (const inner of member.declaration.declarations) { + acc.push(inner.id.value); + } + } else { + acc.push( + member.declaration?.identifier?.value || member.declaration?.id?.value, + ); + } + } + + return acc; +} + +/** + * Get the source code of the file and the exported members; taking into + * consideration the pragmas that affect what members will be available in the + * public API: + * + * - `__private-exports` + * - `export-ignore` + * - `export-ignore [x, y, z]` + * - `export-only [a, b, c]` + * + * @returns [string, string[]] // [source, exports] + */ +export function getFileDetails(path, options = {}) { + const ast = parseFileSync(path, { + ...SWC_OPTIONS, + ...options, + }); + const source = fs.readFileSync(path, 'utf8'); + + const exports = ast.body.reduce(exportsReducer, []); + const [pragma, ...list] = pragmaParser(source); + + const result = [source]; + + switch (pragma) { + case 'ignore': + result.push( + list[0] === '*' ? [] : exports.filter((name) => !list.includes(name)), + ); + break; + case 'only': + result.push(exports.filter((name) => list.includes(name))); + break; + default: + result.push(exports); + break; + } + + return result; +} diff --git a/scripts/pragma-parser.mjs b/scripts/pragma-parser.mjs new file mode 100644 index 00000000..f6c21cd0 --- /dev/null +++ b/scripts/pragma-parser.mjs @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Hypergiant Galactic Systems Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +const rCleaner = /,\s*/; +const rPragma = /\/\/\s*@export-(ignore|only)(?:\s*\[([^\]]+)\])?/; +const rPrivate = /\/\/\s*__private-exports/i; + +/** + * Look for pragmas in the file that would change which exports are considered for the public API: + * + * - `__private-exports` + * - `export-ignore` + * - `export-ignore [x, y, z]` + * - `export-only [a, b, c]` + * + * @returns string[] // [ignore|only, ...members] + */ +export function pragmaParser(src) { + if (src.match(rPrivate)?.[0]) { + return ['ignore', '*']; + } + + const found = src.match(rPragma); + + if (!found) { + return []; + } + + const pragmas = found + .slice(1) + .reduce((a, b = '*') => [ + a, + ...(b ? b.replace(rCleaner, ',').split(',') : b), + ]); + + if (pragmas[0] === 'only' && pragmas[1] === '*') { + return []; + } + + return pragmas; +}