parent
25e49b6931
commit
e648502c57
@ -0,0 +1,56 @@
|
||||
import ts from 'typescript';
|
||||
|
||||
/** Declared names used by the README prop-table guard, independent of formatting. */
|
||||
export function identifiersOfSource(source: string, file: string): Set<string> {
|
||||
const scripts = file.endsWith('.svelte')
|
||||
? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)].map((match) => match[1])
|
||||
: [source];
|
||||
const names = new Set<string>();
|
||||
|
||||
function addName(name: ts.Node | undefined): void {
|
||||
if (name && (ts.isIdentifier(name) || ts.isStringLiteral(name))) names.add(name.text);
|
||||
}
|
||||
|
||||
for (const script of scripts) {
|
||||
const root = ts.createSourceFile(
|
||||
file + '.ts',
|
||||
script,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
ts.ScriptKind.TS
|
||||
);
|
||||
function visit(node: ts.Node): void {
|
||||
if (
|
||||
ts.isPropertySignature(node) ||
|
||||
ts.isPropertyDeclaration(node) ||
|
||||
ts.isMethodSignature(node) ||
|
||||
ts.isMethodDeclaration(node) ||
|
||||
ts.isGetAccessorDeclaration(node) ||
|
||||
ts.isSetAccessorDeclaration(node) ||
|
||||
ts.isPropertyAssignment(node) ||
|
||||
ts.isShorthandPropertyAssignment(node) ||
|
||||
ts.isParameter(node) ||
|
||||
ts.isVariableDeclaration(node) ||
|
||||
ts.isImportClause(node) ||
|
||||
ts.isImportSpecifier(node) ||
|
||||
ts.isExportSpecifier(node) ||
|
||||
ts.isNamespaceImport(node) ||
|
||||
ts.isClassDeclaration(node) ||
|
||||
ts.isInterfaceDeclaration(node) ||
|
||||
ts.isTypeAliasDeclaration(node) ||
|
||||
ts.isFunctionDeclaration(node) ||
|
||||
ts.isEnumDeclaration(node) ||
|
||||
ts.isEnumMember(node)
|
||||
) {
|
||||
addName(node.name);
|
||||
} else if (ts.isBindingElement(node)) {
|
||||
addName(node.propertyName);
|
||||
addName(node.name);
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
visit(root);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { identifiersOfSource } from '../../scripts/docs-check-identifiers';
|
||||
|
||||
describe('README prop declaration index', () => {
|
||||
it('finds the same declarations in inline and wrapped TypeScript', () => {
|
||||
const inline = `
|
||||
import { source, other as local } from './source';
|
||||
export { source, local as exported };
|
||||
export type Props = { value?: string; onValueChange?: () => void };
|
||||
const { open, disabled: isDisabled } = $props<Props>();
|
||||
`;
|
||||
const wrapped = `
|
||||
import {
|
||||
source,
|
||||
other as local
|
||||
} from './source';
|
||||
export {
|
||||
source,
|
||||
local as exported
|
||||
};
|
||||
export type Props = {
|
||||
value?: string;
|
||||
onValueChange?: () => void;
|
||||
};
|
||||
const {
|
||||
open,
|
||||
disabled: isDisabled
|
||||
} = $props<Props>();
|
||||
`;
|
||||
|
||||
expect(identifiersOfSource(inline, 'types.ts')).toEqual(
|
||||
identifiersOfSource(wrapped, 'types.ts')
|
||||
);
|
||||
expect(identifiersOfSource(inline, 'types.ts')).toEqual(
|
||||
new Set([
|
||||
'source',
|
||||
'local',
|
||||
'exported',
|
||||
'Props',
|
||||
'value',
|
||||
'onValueChange',
|
||||
'open',
|
||||
'disabled',
|
||||
'isDisabled'
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('reads Svelte scripts but not markup, comments or string contents', () => {
|
||||
const source = `
|
||||
<script lang="ts">
|
||||
// phantom?: string
|
||||
const note = 'invented: true';
|
||||
let { value, disabled: isDisabled } = $props<{ value: string; disabled?: boolean }>();
|
||||
</script>
|
||||
<div data-visual="mark" />
|
||||
`;
|
||||
const names = identifiersOfSource(source, 'example.svelte');
|
||||
expect(names).toEqual(new Set(['note', 'value', 'disabled', 'isDisabled']));
|
||||
expect(names.has('phantom')).toBe(false);
|
||||
expect(names.has('invented')).toBe(false);
|
||||
expect(names.has('visual')).toBe(false);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in new issue