Skip to content

Commit

Permalink
broke modules out
Browse files Browse the repository at this point in the history
  • Loading branch information
kalisjoshua committed Nov 21, 2024
1 parent 7c43fb5 commit 230825d
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 117 deletions.
155 changes: 38 additions & 117 deletions scripts/docblock.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
80 changes: 80 additions & 0 deletions scripts/file-details.mjs
Original file line number Diff line number Diff line change
@@ -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;
}
50 changes: 50 additions & 0 deletions scripts/pragma-parser.mjs
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 230825d

Please sign in to comment.