@ttt-productions/repo-guards 0.1.32 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/core-definitions/app-routes.d.ts +2 -0
- package/dist/modules/core-definitions/app-routes.d.ts.map +1 -1
- package/dist/modules/core-definitions/app-routes.js +10 -6
- package/dist/modules/core-definitions/app-routes.js.map +1 -1
- package/dist/modules/firestore/path-guards.d.ts +2 -1
- package/dist/modules/firestore/path-guards.d.ts.map +1 -1
- package/dist/modules/firestore/path-guards.js +20 -4
- package/dist/modules/firestore/path-guards.js.map +1 -1
- package/dist/modules/ui/truncation.d.ts +1 -0
- package/dist/modules/ui/truncation.d.ts.map +1 -1
- package/dist/modules/ui/truncation.js +8 -2
- package/dist/modules/ui/truncation.js.map +1 -1
- package/package.json +1 -1
|
@@ -15,6 +15,8 @@ export declare function literalTexts(file: FileView): Array<{
|
|
|
15
15
|
text: string;
|
|
16
16
|
index: number;
|
|
17
17
|
}>;
|
|
18
|
+
export declare const routePrefixPattern: (prefix: string) => RegExp;
|
|
19
|
+
export declare function routePrefixProblems(value: unknown, field: string, problems: string[]): void;
|
|
18
20
|
export declare function routeLiterals(file: FileView, routePrefixes: readonly string[]): Hit[];
|
|
19
21
|
export declare const appRouteOwnerGuard: GenericGuardDefinition<AppRouteOwnerFacts>;
|
|
20
22
|
//# sourceMappingURL=app-routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-routes.d.ts","sourceRoot":"","sources":["../../../src/modules/core-definitions/app-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAqB,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAkBrE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBnF;
|
|
1
|
+
{"version":3,"file":"app-routes.d.ts","sourceRoot":"","sources":["../../../src/modules/core-definitions/app-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAqB,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAkBrE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBnF;AAED,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,KAAG,MACD,CAAC;AAEpD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAO3F;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,EAAE,CAKrF;AAoID,eAAO,MAAM,kBAAkB,EAAE,sBAAsB,CAAC,kBAAkB,CAsFzE,CAAC"}
|
|
@@ -26,9 +26,16 @@ export function literalTexts(file) {
|
|
|
26
26
|
visit(sf);
|
|
27
27
|
return out;
|
|
28
28
|
}
|
|
29
|
-
const
|
|
29
|
+
export const routePrefixPattern = (prefix) => new RegExp(`^${escapeRegExp(prefix)}(?:$|[/?#])`);
|
|
30
|
+
export function routePrefixProblems(value, field, problems) {
|
|
31
|
+
if (!Array.isArray(value))
|
|
32
|
+
return;
|
|
33
|
+
for (const prefix of value)
|
|
34
|
+
if (isText(prefix) && !/^\/[^/?#\s]+(?:\/[^/?#\s]+)*$/.test(prefix))
|
|
35
|
+
problems.push(`${field} entry ${prefix} must start with / and name whole segments, with no trailing /`);
|
|
36
|
+
}
|
|
30
37
|
export function routeLiterals(file, routePrefixes) {
|
|
31
|
-
const patterns = routePrefixes.map(
|
|
38
|
+
const patterns = routePrefixes.map(routePrefixPattern);
|
|
32
39
|
return literalTexts(file)
|
|
33
40
|
.filter(({ text }) => patterns.some((p) => p.test(text)))
|
|
34
41
|
.map(({ text, index }) => ({ index, detail: text.slice(0, 80) }));
|
|
@@ -160,10 +167,7 @@ export const appRouteOwnerGuard = {
|
|
|
160
167
|
identifierProblem(facts.owner.name, 'owner.name', problems);
|
|
161
168
|
}
|
|
162
169
|
namesProblem(facts?.routePrefixes, 'routePrefixes', problems);
|
|
163
|
-
|
|
164
|
-
for (const prefix of facts.routePrefixes)
|
|
165
|
-
if (isText(prefix) && !/^\/[^/?#\s]+(?:\/[^/?#\s]+)*$/.test(prefix))
|
|
166
|
-
problems.push(`routePrefixes entry ${prefix} must start with / and name whole segments, with no trailing /`);
|
|
170
|
+
routePrefixProblems(facts?.routePrefixes, 'routePrefixes', problems);
|
|
167
171
|
if (!Array.isArray(facts?.trees) || !facts.trees.length)
|
|
168
172
|
problems.push('trees is missing');
|
|
169
173
|
else
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-routes.js","sourceRoot":"","sources":["../../../src/modules/core-definitions/app-routes.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, GuardContext } from '../../guard.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { FileView } from '../../reader/view.js';\nimport { reviewedAllowlist, type Hit } from '../../shapes/shapes.js';\nimport {\n identifierProblem,\n isText,\n namesProblem,\n objectProblem,\n pathProblem,\n reviewedProblems,\n textProblem,\n} from '../../support/facts.js';\nimport { escapeRegExp, isUnder } from '../../support/text.js';\nimport { exportedNames } from '../ui/source.js';\nimport { installedManifest, splitBareSpecifier } from '../ui/stylesheets.js';\nimport { kindOfFile } from '../../node/layout.js';\nimport { moduleImports, type ImportKind } from '../../support/imports.js';\nimport { resolveSpecifier } from '../../support/resolve-specifier.js';\nimport { APP_SOURCE } from './shared.js';\n\nexport interface AppRouteOwnerFacts {\n owner: { module: string; name: string };\n routePrefixes: readonly string[];\n trees: readonly string[];\n exceptions: readonly Reviewed[];\n}\n\nexport function literalTexts(file: FileView): Array<{ text: string; index: number }> {\n const sf = file.sourceFile;\n const out: Array<{ text: string; index: number }> = [];\n const visit = (node: ts.Node): void => {\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {\n out.push({ text: node.text, index: node.getStart(sf) });\n return;\n }\n if (ts.isTemplateExpression(node)) {\n out.push({ text: node.head.text, index: node.getStart(sf) });\n for (const span of node.templateSpans)\n out.push({ text: span.literal.text, index: span.literal.getStart(sf) });\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return out;\n}\n\nconst prefixPattern = (prefix: string) => new RegExp(`^${escapeRegExp(prefix)}(?:$|[/?#])`);\n\nexport function routeLiterals(file: FileView, routePrefixes: readonly string[]): Hit[] {\n const patterns = routePrefixes.map(prefixPattern);\n return literalTexts(file)\n .filter(({ text }) => patterns.some((p) => p.test(text)))\n .map(({ text, index }) => ({ index, detail: text.slice(0, 80) }));\n}\n\nfunction pickEntry(entry: unknown): string | null {\n if (typeof entry === 'string') return entry;\n if (entry && typeof entry === 'object') {\n const conditions = entry as Record<string, unknown>;\n for (const key of ['import', 'module', 'default', 'require', 'node']) {\n const found = pickEntry(conditions[key]);\n if (found) return found;\n }\n }\n return null;\n}\n\nfunction installedEntry(context: GuardContext, module: string): string | null {\n const { name, subpath } = splitBareSpecifier(module);\n const manifest = installedManifest(context, name);\n if (!manifest) return null;\n const exportsField = manifest.exports;\n const keyed =\n !!exportsField &&\n typeof exportsField === 'object' &&\n Object.keys(exportsField).some((k) => k.startsWith('.'));\n const fallback =\n typeof manifest.module === 'string'\n ? manifest.module\n : typeof manifest.main === 'string'\n ? manifest.main\n : './index.js';\n const target = keyed\n ? pickEntry((exportsField as Record<string, unknown>)[subpath])\n : exportsField !== undefined\n ? subpath === '.'\n ? pickEntry(exportsField)\n : null\n : subpath !== '.'\n ? subpath\n : fallback;\n if (!target) return null;\n const base = `node_modules/${name}/package.json`;\n const resolved = resolveSpecifier(\n context.reader,\n base,\n target.startsWith('.') ? target : `./${target}`,\n [],\n );\n return context.reader.exists(resolved) ? resolved : null;\n}\n\nfunction localLoads(context: GuardContext, file: FileView, kinds: readonly ImportKind[]) {\n const out: string[] = [];\n for (const site of moduleImports(file.sourceFile, kinds)) {\n if (!site.spec.startsWith('.')) continue;\n const resolved = resolveSpecifier(context.reader, file.path, site.spec, []);\n if (\n context.reader.exists(resolved) &&\n kindOfFile(resolved.slice(resolved.lastIndexOf('/') + 1)) === 'code'\n )\n out.push(resolved);\n }\n return out;\n}\n\nfunction ownerCode(\n context: GuardContext,\n module: string,\n): { files: FileView[]; entry: FileView; where: string } | null {\n const entryPath = context.reader.exists(module) ? module : installedEntry(context, module);\n if (!entryPath) return null;\n const entry = context.file(entryPath);\n const seen = new Map<string, FileView>([[entryPath, entry]]);\n const queue = [entry];\n while (queue.length) {\n const file = queue.shift()!;\n for (const path of localLoads(context, file, ['import', 'export'])) {\n if (seen.has(path)) continue;\n const next = context.file(path);\n seen.set(path, next);\n queue.push(next);\n }\n }\n return { files: [...seen.values()], entry, where: module };\n}\n\nfunction exportsName(\n context: GuardContext,\n file: FileView,\n name: string,\n seen: Set<string> = new Set(),\n): boolean {\n const key = `${file.path}#${name}`;\n if (seen.has(key)) return false;\n seen.add(key);\n const target = (spec: ts.Expression): FileView | null => {\n if (!ts.isStringLiteral(spec) || !spec.text.startsWith('.')) return null;\n const resolved = resolveSpecifier(context.reader, file.path, spec.text, []);\n return context.reader.exists(resolved) ? context.file(resolved) : null;\n };\n for (const statement of file.sourceFile.statements) {\n if (!ts.isExportDeclaration(statement)) continue;\n const clause = statement.exportClause;\n if (!statement.moduleSpecifier) {\n if (clause && ts.isNamedExports(clause) && clause.elements.some((e) => e.name.text === name))\n return true;\n continue;\n }\n if (clause && ts.isNamespaceExport(clause)) {\n if (clause.name.text === name) return true;\n continue;\n }\n const from = target(statement.moduleSpecifier);\n if (!from) continue;\n if (!clause) {\n if (exportsName(context, from, name, seen)) return true;\n continue;\n }\n const element = clause.elements.find((e) => e.name.text === name);\n if (element)\n return exportsName(context, from, (element.propertyName ?? element.name).text, seen);\n }\n const reExported = new Set(\n file.sourceFile.statements\n .filter(ts.isExportDeclaration)\n .flatMap((s) =>\n s.exportClause && ts.isNamedExports(s.exportClause)\n ? s.exportClause.elements.map((e) => e.name.text)\n : [],\n ),\n );\n return !reExported.has(name) && exportedNames(file).has(name);\n}\n\nexport const appRouteOwnerGuard: GenericGuardDefinition<AppRouteOwnerFacts> = {\n id: 'appRouteOwner',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (objectProblem(facts?.owner, 'owner', problems)) {\n textProblem(facts.owner.module, 'owner.module', problems);\n identifierProblem(facts.owner.name, 'owner.name', problems);\n }\n namesProblem(facts?.routePrefixes, 'routePrefixes', problems);\n if (Array.isArray(facts?.routePrefixes))\n for (const prefix of facts.routePrefixes)\n if (isText(prefix) && !/^\\/[^/?#\\s]+(?:\\/[^/?#\\s]+)*$/.test(prefix))\n problems.push(\n `routePrefixes entry ${prefix} must start with / and name whole segments, with no trailing /`,\n );\n if (!Array.isArray(facts?.trees) || !facts.trees.length) problems.push('trees is missing');\n else facts.trees.forEach((t, i) => pathProblem(t, `trees[${i}]`, problems));\n reviewedProblems(facts?.exceptions, 'exceptions', problems);\n return problems;\n },\n build(facts) {\n const { module, name } = facts.owner;\n const tree = facts.trees[0];\n const [prefix] = facts.routePrefixes;\n const inTrees = (file: FileView) => facts.trees.some((t) => isUnder(file.path, t));\n const guard = reviewedAllowlist({\n title: 'app routes are built by the one route owner',\n description:\n `An app page path is declared once and built through \\`${name}\\` from ${module}. Under ${facts.trees.join(', ')}, ` +\n `no other code holds a string or template literal that starts with one of the page roots ` +\n `(${facts.routePrefixes.join(', ')}) as a whole segment — in an href, a navigation call, a redirect, a link in a ` +\n 'message, or anywhere else: a route written by hand drifts from its declaration the day the page moves. Build ' +\n `the path with \\`${name}\\`. A framework route file or another genuine non-route literal is a reviewed exception ` +\n 'with its why; an exception that no longer holds such a literal is stale, and so is a page root the owner no ' +\n 'longer builds. The root path `/` is not a page root: every root names at least one whole segment.',\n scope: APP_SOURCE,\n match: (file) =>\n inTrees(file) && file.path !== module ? routeLiterals(file, facts.routePrefixes) : [],\n allowlist: facts.exceptions,\n samples: {\n flags: [\n { path: `${tree}/probe.tsx`, text: `<Link href=\"${prefix}\">Open</Link>` },\n { path: `${tree}/probe.tsx`, text: `router.push(\\`${prefix}/\\${id}/edit\\`);` },\n { path: `${tree}/probe.ts`, text: `redirect('${prefix}?tab=a');` },\n { path: `${tree}/probe.ts`, text: `const url = \\`\\${origin}${prefix}/\\${id}\\`;` },\n ],\n passes: [\n { path: `${tree}/probe.tsx`, text: `<Link href={${name}.home()}>Open</Link>` },\n { path: `${tree}/probe.ts`, text: `const other = '${prefix}x';` },\n { path: `${tree}/probe.ts`, text: `// the page lives at ${prefix}\\nconst n = 1;` },\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings: Finding[] = [...(await guard.run(context))];\n const scanned = context.files(APP_SOURCE).filter(inTrees);\n for (const t of facts.trees)\n if (!scanned.some((f) => isUnder(f.path, t)))\n findings.push({\n file: t,\n detail: 'the tree holds no production code the guard can read',\n });\n const owner = ownerCode(context, module);\n if (!owner) {\n findings.push({\n file: module,\n detail:\n 'the route owner cannot be read: no such file, and no installed entry for that subpath',\n });\n return findings;\n }\n if (!exportsName(context, owner.entry, name))\n findings.push({\n file: module,\n detail: `the route owner no longer exports ${name} (read from ${owner.where})`,\n });\n for (const p of facts.routePrefixes)\n if (!owner.files.some((f) => routeLiterals(f, [p]).length))\n findings.push({\n file: '(routePrefixes)',\n detail: `stale page root: ${owner.where} no longer builds any path under ${p}`,\n });\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"app-routes.js","sourceRoot":"","sources":["../../../src/modules/core-definitions/app-routes.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, GuardContext } from '../../guard.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { FileView } from '../../reader/view.js';\nimport { reviewedAllowlist, type Hit } from '../../shapes/shapes.js';\nimport {\n identifierProblem,\n isText,\n namesProblem,\n objectProblem,\n pathProblem,\n reviewedProblems,\n textProblem,\n} from '../../support/facts.js';\nimport { escapeRegExp, isUnder } from '../../support/text.js';\nimport { exportedNames } from '../ui/source.js';\nimport { installedManifest, splitBareSpecifier } from '../ui/stylesheets.js';\nimport { kindOfFile } from '../../node/layout.js';\nimport { moduleImports, type ImportKind } from '../../support/imports.js';\nimport { resolveSpecifier } from '../../support/resolve-specifier.js';\nimport { APP_SOURCE } from './shared.js';\n\nexport interface AppRouteOwnerFacts {\n owner: { module: string; name: string };\n routePrefixes: readonly string[];\n trees: readonly string[];\n exceptions: readonly Reviewed[];\n}\n\nexport function literalTexts(file: FileView): Array<{ text: string; index: number }> {\n const sf = file.sourceFile;\n const out: Array<{ text: string; index: number }> = [];\n const visit = (node: ts.Node): void => {\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {\n out.push({ text: node.text, index: node.getStart(sf) });\n return;\n }\n if (ts.isTemplateExpression(node)) {\n out.push({ text: node.head.text, index: node.getStart(sf) });\n for (const span of node.templateSpans)\n out.push({ text: span.literal.text, index: span.literal.getStart(sf) });\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return out;\n}\n\nexport const routePrefixPattern = (prefix: string): RegExp =>\n new RegExp(`^${escapeRegExp(prefix)}(?:$|[/?#])`);\n\nexport function routePrefixProblems(value: unknown, field: string, problems: string[]): void {\n if (!Array.isArray(value)) return;\n for (const prefix of value)\n if (isText(prefix) && !/^\\/[^/?#\\s]+(?:\\/[^/?#\\s]+)*$/.test(prefix))\n problems.push(\n `${field} entry ${prefix} must start with / and name whole segments, with no trailing /`,\n );\n}\n\nexport function routeLiterals(file: FileView, routePrefixes: readonly string[]): Hit[] {\n const patterns = routePrefixes.map(routePrefixPattern);\n return literalTexts(file)\n .filter(({ text }) => patterns.some((p) => p.test(text)))\n .map(({ text, index }) => ({ index, detail: text.slice(0, 80) }));\n}\n\nfunction pickEntry(entry: unknown): string | null {\n if (typeof entry === 'string') return entry;\n if (entry && typeof entry === 'object') {\n const conditions = entry as Record<string, unknown>;\n for (const key of ['import', 'module', 'default', 'require', 'node']) {\n const found = pickEntry(conditions[key]);\n if (found) return found;\n }\n }\n return null;\n}\n\nfunction installedEntry(context: GuardContext, module: string): string | null {\n const { name, subpath } = splitBareSpecifier(module);\n const manifest = installedManifest(context, name);\n if (!manifest) return null;\n const exportsField = manifest.exports;\n const keyed =\n !!exportsField &&\n typeof exportsField === 'object' &&\n Object.keys(exportsField).some((k) => k.startsWith('.'));\n const fallback =\n typeof manifest.module === 'string'\n ? manifest.module\n : typeof manifest.main === 'string'\n ? manifest.main\n : './index.js';\n const target = keyed\n ? pickEntry((exportsField as Record<string, unknown>)[subpath])\n : exportsField !== undefined\n ? subpath === '.'\n ? pickEntry(exportsField)\n : null\n : subpath !== '.'\n ? subpath\n : fallback;\n if (!target) return null;\n const base = `node_modules/${name}/package.json`;\n const resolved = resolveSpecifier(\n context.reader,\n base,\n target.startsWith('.') ? target : `./${target}`,\n [],\n );\n return context.reader.exists(resolved) ? resolved : null;\n}\n\nfunction localLoads(context: GuardContext, file: FileView, kinds: readonly ImportKind[]) {\n const out: string[] = [];\n for (const site of moduleImports(file.sourceFile, kinds)) {\n if (!site.spec.startsWith('.')) continue;\n const resolved = resolveSpecifier(context.reader, file.path, site.spec, []);\n if (\n context.reader.exists(resolved) &&\n kindOfFile(resolved.slice(resolved.lastIndexOf('/') + 1)) === 'code'\n )\n out.push(resolved);\n }\n return out;\n}\n\nfunction ownerCode(\n context: GuardContext,\n module: string,\n): { files: FileView[]; entry: FileView; where: string } | null {\n const entryPath = context.reader.exists(module) ? module : installedEntry(context, module);\n if (!entryPath) return null;\n const entry = context.file(entryPath);\n const seen = new Map<string, FileView>([[entryPath, entry]]);\n const queue = [entry];\n while (queue.length) {\n const file = queue.shift()!;\n for (const path of localLoads(context, file, ['import', 'export'])) {\n if (seen.has(path)) continue;\n const next = context.file(path);\n seen.set(path, next);\n queue.push(next);\n }\n }\n return { files: [...seen.values()], entry, where: module };\n}\n\nfunction exportsName(\n context: GuardContext,\n file: FileView,\n name: string,\n seen: Set<string> = new Set(),\n): boolean {\n const key = `${file.path}#${name}`;\n if (seen.has(key)) return false;\n seen.add(key);\n const target = (spec: ts.Expression): FileView | null => {\n if (!ts.isStringLiteral(spec) || !spec.text.startsWith('.')) return null;\n const resolved = resolveSpecifier(context.reader, file.path, spec.text, []);\n return context.reader.exists(resolved) ? context.file(resolved) : null;\n };\n for (const statement of file.sourceFile.statements) {\n if (!ts.isExportDeclaration(statement)) continue;\n const clause = statement.exportClause;\n if (!statement.moduleSpecifier) {\n if (clause && ts.isNamedExports(clause) && clause.elements.some((e) => e.name.text === name))\n return true;\n continue;\n }\n if (clause && ts.isNamespaceExport(clause)) {\n if (clause.name.text === name) return true;\n continue;\n }\n const from = target(statement.moduleSpecifier);\n if (!from) continue;\n if (!clause) {\n if (exportsName(context, from, name, seen)) return true;\n continue;\n }\n const element = clause.elements.find((e) => e.name.text === name);\n if (element)\n return exportsName(context, from, (element.propertyName ?? element.name).text, seen);\n }\n const reExported = new Set(\n file.sourceFile.statements\n .filter(ts.isExportDeclaration)\n .flatMap((s) =>\n s.exportClause && ts.isNamedExports(s.exportClause)\n ? s.exportClause.elements.map((e) => e.name.text)\n : [],\n ),\n );\n return !reExported.has(name) && exportedNames(file).has(name);\n}\n\nexport const appRouteOwnerGuard: GenericGuardDefinition<AppRouteOwnerFacts> = {\n id: 'appRouteOwner',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (objectProblem(facts?.owner, 'owner', problems)) {\n textProblem(facts.owner.module, 'owner.module', problems);\n identifierProblem(facts.owner.name, 'owner.name', problems);\n }\n namesProblem(facts?.routePrefixes, 'routePrefixes', problems);\n routePrefixProblems(facts?.routePrefixes, 'routePrefixes', problems);\n if (!Array.isArray(facts?.trees) || !facts.trees.length) problems.push('trees is missing');\n else facts.trees.forEach((t, i) => pathProblem(t, `trees[${i}]`, problems));\n reviewedProblems(facts?.exceptions, 'exceptions', problems);\n return problems;\n },\n build(facts) {\n const { module, name } = facts.owner;\n const tree = facts.trees[0];\n const [prefix] = facts.routePrefixes;\n const inTrees = (file: FileView) => facts.trees.some((t) => isUnder(file.path, t));\n const guard = reviewedAllowlist({\n title: 'app routes are built by the one route owner',\n description:\n `An app page path is declared once and built through \\`${name}\\` from ${module}. Under ${facts.trees.join(', ')}, ` +\n `no other code holds a string or template literal that starts with one of the page roots ` +\n `(${facts.routePrefixes.join(', ')}) as a whole segment — in an href, a navigation call, a redirect, a link in a ` +\n 'message, or anywhere else: a route written by hand drifts from its declaration the day the page moves. Build ' +\n `the path with \\`${name}\\`. A framework route file or another genuine non-route literal is a reviewed exception ` +\n 'with its why; an exception that no longer holds such a literal is stale, and so is a page root the owner no ' +\n 'longer builds. The root path `/` is not a page root: every root names at least one whole segment.',\n scope: APP_SOURCE,\n match: (file) =>\n inTrees(file) && file.path !== module ? routeLiterals(file, facts.routePrefixes) : [],\n allowlist: facts.exceptions,\n samples: {\n flags: [\n { path: `${tree}/probe.tsx`, text: `<Link href=\"${prefix}\">Open</Link>` },\n { path: `${tree}/probe.tsx`, text: `router.push(\\`${prefix}/\\${id}/edit\\`);` },\n { path: `${tree}/probe.ts`, text: `redirect('${prefix}?tab=a');` },\n { path: `${tree}/probe.ts`, text: `const url = \\`\\${origin}${prefix}/\\${id}\\`;` },\n ],\n passes: [\n { path: `${tree}/probe.tsx`, text: `<Link href={${name}.home()}>Open</Link>` },\n { path: `${tree}/probe.ts`, text: `const other = '${prefix}x';` },\n { path: `${tree}/probe.ts`, text: `// the page lives at ${prefix}\\nconst n = 1;` },\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings: Finding[] = [...(await guard.run(context))];\n const scanned = context.files(APP_SOURCE).filter(inTrees);\n for (const t of facts.trees)\n if (!scanned.some((f) => isUnder(f.path, t)))\n findings.push({\n file: t,\n detail: 'the tree holds no production code the guard can read',\n });\n const owner = ownerCode(context, module);\n if (!owner) {\n findings.push({\n file: module,\n detail:\n 'the route owner cannot be read: no such file, and no installed entry for that subpath',\n });\n return findings;\n }\n if (!exportsName(context, owner.entry, name))\n findings.push({\n file: module,\n detail: `the route owner no longer exports ${name} (read from ${owner.where})`,\n });\n for (const p of facts.routePrefixes)\n if (!owner.files.some((f) => routeLiterals(f, [p]).length))\n findings.push({\n file: '(routePrefixes)',\n detail: `stale page root: ${owner.where} no longer builds any path under ${p}`,\n });\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
@@ -16,13 +16,14 @@ export interface RetiredSegmentFacts {
|
|
|
16
16
|
name: string;
|
|
17
17
|
replacement: string;
|
|
18
18
|
}>;
|
|
19
|
+
routePrefixes: readonly string[];
|
|
19
20
|
reviewedMatches: ReadonlyArray<{
|
|
20
21
|
file: string;
|
|
21
22
|
contains: string;
|
|
22
23
|
why: string;
|
|
23
24
|
}>;
|
|
24
25
|
}
|
|
25
|
-
export declare function retiredSegmentHits(file: FileView, names: readonly string[]): Array<NodeHit & {
|
|
26
|
+
export declare function retiredSegmentHits(file: FileView, names: readonly string[], routePrefixes: readonly string[]): Array<NodeHit & {
|
|
26
27
|
name: string;
|
|
27
28
|
text: string;
|
|
28
29
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-guards.d.ts","sourceRoot":"","sources":["../../../src/modules/firestore/path-guards.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIrD,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,OAAO,EACb,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"path-guards.d.ts","sourceRoot":"","sources":["../../../src/modules/firestore/path-guards.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIrD,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,OAAO,EACb,MAAM,cAAc,CAAC;AAuCtB,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,EAAE,CAkC5D;AAED,eAAO,MAAM,mBAAmB,EAAE,sBAAsB,CAAC,OAAO,CAmB/D,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,aAAa,EAAE,SAAS,QAAQ,EAAE,CAAC;CACpC;AAYD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,gBAAgB,GAAG,qBAAqB,CAAC,GACvE,OAAO,EAAE,CAuDX;AAED,eAAO,MAAM,iBAAiB,EAAE,sBAAsB,CAAC,iBAAiB,CA4CvE,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,eAAe,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjF;AAID,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,KAAK,CAAC,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA6CjD;AAED,eAAO,MAAM,mBAAmB,EAAE,sBAAsB,CAAC,mBAAmB,CA+E3E,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { namesProblem, reviewedProblems, takesNoFacts, isList } from '../../supp
|
|
|
4
4
|
import { escapeRegExp } from '../../support/text.js';
|
|
5
5
|
import { bannedGuard, findingsOf, isMemberOf, literalBindings, } from './support.js';
|
|
6
6
|
import { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';
|
|
7
|
+
import { routePrefixPattern, routePrefixProblems } from '../core-definitions/app-routes.js';
|
|
7
8
|
const ACCESSORS = new Set(['collection', 'collectionGroup', 'doc']);
|
|
8
9
|
const APP_CODE = {
|
|
9
10
|
roots: ['app', 'functions'],
|
|
@@ -196,14 +197,23 @@ export const pathAssemblyGuard = {
|
|
|
196
197
|
];
|
|
197
198
|
},
|
|
198
199
|
};
|
|
199
|
-
|
|
200
|
+
const SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
201
|
+
export function retiredSegmentHits(file, names, routePrefixes) {
|
|
200
202
|
const source = file.sourceFile;
|
|
201
203
|
const retired = new Set(names);
|
|
202
204
|
const alternation = names.map(escapeRegExp).join('|');
|
|
203
205
|
const inside = new RegExp(`/(${alternation})(?=/)`, 'g');
|
|
204
206
|
const closing = new RegExp(`/(${alternation})$`);
|
|
207
|
+
const routes = routePrefixes.map(routePrefixPattern);
|
|
208
|
+
const specifiers = new Set(file.imports.map((site) => site.specifier));
|
|
205
209
|
const hits = [];
|
|
206
210
|
const scan = (node, parts) => {
|
|
211
|
+
const opening = parts[0].text;
|
|
212
|
+
const whole = parts.map((p) => p.text).join('');
|
|
213
|
+
if (specifiers.has(node) ||
|
|
214
|
+
routes.some((route) => route.test(opening)) ||
|
|
215
|
+
SOURCE_FILE.test(whole))
|
|
216
|
+
return;
|
|
207
217
|
const found = [];
|
|
208
218
|
for (const { text, closes } of parts) {
|
|
209
219
|
for (const m of text.matchAll(inside))
|
|
@@ -246,6 +256,8 @@ export const retiredSegmentGuard = {
|
|
|
246
256
|
id: (e) => typeof e.contains === 'string' && e.contains ? `${e.file} ${e.contains}` : undefined,
|
|
247
257
|
idNames: 'text it contains',
|
|
248
258
|
});
|
|
259
|
+
namesProblem(facts?.routePrefixes, 'routePrefixes', problems, { nonEmpty: false });
|
|
260
|
+
routePrefixProblems(facts?.routePrefixes, 'routePrefixes', problems);
|
|
249
261
|
if (!isList(facts?.segments) || !facts.segments.length)
|
|
250
262
|
return [...problems, 'segments is empty (declare the guard absent instead)'];
|
|
251
263
|
const names = facts.segments.map((s) => s?.name);
|
|
@@ -274,14 +286,18 @@ export const retiredSegmentGuard = {
|
|
|
274
286
|
description: 'A segment name inside a string is never type-checked, so a retired name keeps addressing a collection that no ' +
|
|
275
287
|
'longer exists — a trigger that never fires, a read that returns nothing, a fixture nobody reads. Flagged: a ' +
|
|
276
288
|
'retired name in path position in any string or template (`/name/`, or `/name` closing it), and a retired name ' +
|
|
277
|
-
|
|
278
|
-
'
|
|
289
|
+
'as a collectionGroup id. Never flagged: a module specifier (an import, re-export, `import()`, `require()`, ' +
|
|
290
|
+
'test-runner mock, or type import), a string or template that opens with one of the app page roots ' +
|
|
291
|
+
`(${facts.routePrefixes.join(', ') || 'none declared'}) as a whole segment, and a string or template that ` +
|
|
292
|
+
'names a source file (it ends in a .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, or .cts extension). Fix: build the path ' +
|
|
293
|
+
"through the core. A reviewed match names a literal's text that is not a Firestore path, with its why; one that " +
|
|
294
|
+
'matches nothing is stale.',
|
|
279
295
|
timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,
|
|
280
296
|
run(context) {
|
|
281
297
|
const findings = [];
|
|
282
298
|
const used = new Set();
|
|
283
299
|
for (const file of context.files(scope)) {
|
|
284
|
-
for (const hit of retiredSegmentHits(file, names)) {
|
|
300
|
+
for (const hit of retiredSegmentHits(file, names, facts.routePrefixes)) {
|
|
285
301
|
const index = facts.reviewedMatches.findIndex((r) => r.file === file.path && hit.text.includes(r.contains));
|
|
286
302
|
if (index !== -1) {
|
|
287
303
|
used.add(index);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-guards.js","sourceRoot":"","sources":["../../../src/modules/firestore/path-guards.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { Finding } from '../../guard.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { FileView } from '../../reader/view.js';\nimport { calleeName, forEachNode, isPlainString, lineOfNode, unwrap } from '../../support/ast.js';\nimport { namesProblem, reviewedProblems, takesNoFacts, isList } from '../../support/facts.js';\nimport { escapeRegExp } from '../../support/text.js';\nimport {\n bannedGuard,\n findingsOf,\n isMemberOf,\n literalBindings,\n type NoFacts,\n type NodeHit,\n} from './support.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\n\nconst ACCESSORS = new Set(['collection', 'collectionGroup', 'doc']);\n\nconst APP_CODE: Scope = {\n roots: ['app', 'functions'],\n optionalRoots: ['functions'],\n kinds: ['code'],\n tests: false,\n};\n\nfunction isHandle(node: ts.Expression): boolean {\n const plain = (n: ts.Expression): boolean =>\n ts.isIdentifier(n) || (ts.isPropertyAccessExpression(n) && plain(n.expression));\n return (\n plain(node) ||\n (ts.isCallExpression(node) && node.arguments.length === 0 && plain(node.expression))\n );\n}\n\nfunction accessorPathArguments(\n call: ts.CallExpression,\n): { form: 'method' | 'modular'; args: readonly ts.Expression[] } | null {\n const callee = call.expression;\n if (ts.isPropertyAccessExpression(callee) && ACCESSORS.has(callee.name.text))\n return { form: 'method', args: call.arguments };\n if (\n ts.isIdentifier(callee) &&\n ACCESSORS.has(callee.text) &&\n call.arguments.length >= 2 &&\n isHandle(call.arguments[0])\n ) {\n return { form: 'modular', args: call.arguments.slice(1) };\n }\n return null;\n}\n\nexport function literalSegmentHits(file: FileView): NodeHit[] {\n const source = file.sourceFile;\n const bindings = literalBindings(source);\n const typed = (arg: ts.Expression): string | null => {\n const value = unwrap(arg);\n if (isPlainString(value)) return `'${value.text}'`;\n if (ts.isIdentifier(value) && bindings.has(value.text))\n return `${value.text} (bound to '${bindings.get(value.text)}')`;\n return null;\n };\n const hits: NodeHit[] = [];\n forEachNode(source, (node) => {\n if (!ts.isCallExpression(node)) return;\n const accessor = accessorPathArguments(node);\n if (!accessor) return;\n const name = calleeName(node);\n if (accessor.form === 'method') {\n // The Admin SDK accessor takes the whole segment as its one argument.\n if (accessor.args.length !== 1) return;\n const segment = typed(accessor.args[0]);\n if (segment)\n hits.push({\n node: (node.expression as ts.PropertyAccessExpression).name,\n detail: `.${name}(${segment})`,\n });\n return;\n }\n const segments = accessor.args\n .filter((arg) => !ts.isSpreadElement(arg))\n .map(typed)\n .filter((s): s is string => s !== null);\n if (segments.length) hits.push({ node, detail: `${name}(db, …) with ${segments.join(', ')}` });\n });\n return hits;\n}\n\nexport const literalSegmentGuard: GenericGuardDefinition<NoFacts> = {\n id: 'firestoreLiteralSegments',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems: takesNoFacts,\n build() {\n return [\n bannedGuard({\n title: 'no typed path segment reaches a Firestore accessor',\n description:\n 'A collection or document segment comes from the core package, never a string typed at the call. Flagged: a ' +\n 'string literal (or a name bound to one in the same file) as the whole argument of x.collection / ' +\n 'x.collectionGroup / x.doc, on every link of a chain, and in ANY path position after the handle of the ' +\n 'modular collection(db, …) / collectionGroup(db, …) / doc(db, …). A literal whose text equals the constant is ' +\n \"still the violation. Fix: import the owner — the core's collection constant, or its path builder.\",\n scope: APP_CODE,\n match: literalSegmentHits,\n }),\n ];\n },\n};\n\nexport interface PathAssemblyFacts {\n ownerConstants: readonly string[];\n segmentArrayJoiners: readonly string[];\n reviewedSites: readonly Reviewed[];\n}\n\nconst hasSlash = (node: ts.Expression) => isPlainString(node) && node.text.includes('/');\n\nfunction concatenationOperands(node: ts.Expression): ts.Expression[] {\n const value = unwrap(node);\n if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.PlusToken) {\n return [...concatenationOperands(value.left), ...concatenationOperands(value.right)];\n }\n return [value];\n}\n\nexport function pathAssemblyHits(\n file: FileView,\n facts: Pick<PathAssemblyFacts, 'ownerConstants' | 'segmentArrayJoiners'>,\n): NodeHit[] {\n const owners = new Set(facts.ownerConstants);\n const joiners = new Set(facts.segmentArrayJoiners);\n const isOwner = (node: ts.Expression) => isMemberOf(unwrap(node), owners);\n const hits = new Map<number, NodeHit>();\n const hit = (node: ts.Node, detail: string) => {\n const at = node.getStart(file.sourceFile);\n if (!hits.has(at)) hits.set(at, { node, detail });\n };\n forEachNode(file.sourceFile, (node) => {\n if (ts.isCallExpression(node)) {\n const accessor = accessorPathArguments(node);\n for (const raw of accessor?.args ?? []) {\n const arg = unwrap(raw);\n if (ts.isTemplateExpression(arg)) {\n if (arg.templateSpans.some((span) => isOwner(span.expression)))\n hit(arg, 'an owner constant spliced into a template path');\n else if (\n (arg.head.text + arg.templateSpans.map((s) => s.literal.text).join('')).includes('/')\n ) {\n hit(arg, 'a template path built from a `/` and a hole');\n }\n } else if (ts.isBinaryExpression(arg) && concatenationOperands(arg).some(hasSlash)) {\n hit(arg, 'a slash path concatenated at the accessor');\n }\n }\n const name = calleeName(node);\n if (name && joiners.has(name)) {\n for (const arg of node.arguments) {\n const value = unwrap(arg);\n if (\n ts.isArrayLiteralExpression(value) &&\n value.elements.some((e) => !ts.isSpreadElement(e) && isOwner(e))\n ) {\n hit(value, `a segment array handed to ${name}() with an owner constant in it`);\n }\n }\n }\n } else if (ts.isTemplateExpression(node)) {\n node.templateSpans.forEach((span, i) => {\n const followedByHole = i < node.templateSpans.length - 1;\n if (isOwner(span.expression) && span.literal.text === '/' && followedByHole) {\n hit(node, 'an owner constant joined to a hole with a `/`');\n }\n });\n } else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {\n const left = unwrap(node.left);\n const right = unwrap(node.right);\n if ((isOwner(left) && hasSlash(right)) || (hasSlash(left) && isOwner(right)))\n hit(node, 'an owner constant concatenated with a slash');\n }\n });\n return [...hits.values()].sort(\n (a, b) => a.node.getStart(file.sourceFile) - b.node.getStart(file.sourceFile),\n );\n}\n\nexport const pathAssemblyGuard: GenericGuardDefinition<PathAssemblyFacts> = {\n id: 'firestorePathAssembly',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.ownerConstants, 'ownerConstants', problems);\n namesProblem(facts?.segmentArrayJoiners, 'segmentArrayJoiners', problems, { nonEmpty: false });\n reviewedProblems(facts?.reviewedSites, 'reviewedSites', problems);\n return problems;\n },\n build(facts) {\n const reviewed = new Map(facts.reviewedSites.map((s) => [s.path, s]));\n return [\n {\n title: 'no Firestore path is assembled by hand',\n description:\n \"A path's string form derives from the core's path builder; writing it again at the call is a second spelling. \" +\n 'Flagged: an owner constant spliced into an accessor template; an accessor template holding both a `/` and a ' +\n 'hole, in either order; a slash-bearing literal concatenated in an accessor argument; an owner constant joined ' +\n 'to a hole by a `/` in any template, or concatenated with a slash-bearing literal anywhere; a segment array ' +\n \"holding an owner constant handed to a path joiner. Fix: call the core's path builder. A reviewed site is a file \" +\n 'with its why; an entry whose file no longer assembles a path is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const held = new Set<string>();\n for (const file of context.files(APP_CODE)) {\n const hits = pathAssemblyHits(file, facts);\n if (!hits.length) continue;\n if (reviewed.has(file.path)) held.add(file.path);\n else findings.push(...findingsOf(file, hits));\n }\n for (const path of reviewed.keys()) {\n if (!held.has(path))\n findings.push({\n file: path,\n detail: 'stale reviewed site: the file no longer assembles a path (or is gone)',\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n\nexport interface RetiredSegmentFacts {\n segments: ReadonlyArray<{ name: string; replacement: string }>;\n reviewedMatches: ReadonlyArray<{ file: string; contains: string; why: string }>;\n}\n\nexport function retiredSegmentHits(\n file: FileView,\n names: readonly string[],\n): Array<NodeHit & { name: string; text: string }> {\n const source = file.sourceFile;\n const retired = new Set(names);\n const alternation = names.map(escapeRegExp).join('|');\n const inside = new RegExp(`/(${alternation})(?=/)`, 'g');\n const closing = new RegExp(`/(${alternation})$`);\n const hits: Array<NodeHit & { name: string; text: string }> = [];\n const scan = (node: ts.Node, parts: Array<{ text: string; closes: boolean }>) => {\n const found: string[] = [];\n for (const { text, closes } of parts) {\n for (const m of text.matchAll(inside)) found.push(m[1]);\n const end = closes ? closing.exec(text) : null;\n if (end) found.push(end[1]);\n }\n for (const name of found) hits.push({ node, name, text: node.getText(source), detail: '' });\n };\n forEachNode(source, (node) => {\n if (isPlainString(node)) scan(node, [{ text: node.text, closes: true }]);\n else if (ts.isTemplateExpression(node)) {\n scan(node, [\n { text: node.head.text, closes: false },\n ...node.templateSpans.map((s, i) => ({\n text: s.literal.text,\n closes: i === node.templateSpans.length - 1,\n })),\n ]);\n }\n if (ts.isCallExpression(node) && calleeName(node) === 'collectionGroup') {\n for (const arg of node.arguments) {\n if (isPlainString(arg) && retired.has(arg.text))\n hits.push({ node: arg, name: arg.text, text: arg.getText(source), detail: '' });\n }\n }\n });\n return hits;\n}\n\nexport const retiredSegmentGuard: GenericGuardDefinition<RetiredSegmentFacts> = {\n id: 'firestoreRetiredSegments',\n absentWhen: 'the app has retired no Firestore segment name',\n factProblems(facts) {\n const problems: string[] = [];\n reviewedProblems(facts?.reviewedMatches, 'reviewedMatches', problems, {\n key: 'file',\n id: (e) =>\n typeof e.contains === 'string' && e.contains ? `${e.file} ${e.contains}` : undefined,\n idNames: 'text it contains',\n });\n if (!isList(facts?.segments) || !facts.segments.length)\n return [...problems, 'segments is empty (declare the guard absent instead)'];\n const names = facts.segments.map((s) => s?.name);\n for (const s of facts.segments) {\n if (typeof s?.name !== 'string' || !/^[\\w$-]+$/.test(s.name))\n problems.push(`segments: ${String(s?.name)} is not a segment name`);\n if (typeof s?.replacement !== 'string' || !s.replacement.trim())\n problems.push(`segments: ${String(s?.name)} names no replacement`);\n }\n if (new Set(names).size !== names.length) problems.push('segments names one entry twice');\n return problems;\n },\n build(facts) {\n const replacement = new Map(facts.segments.map((s) => [s.name, s.replacement]));\n const names = [...replacement.keys()];\n const scope: Scope = {\n roots: ['app', 'functions', 'scripts', 'e2e', 'hostedE2e', 'workers'],\n optionalRoots: ['functions', 'scripts', 'e2e', 'hostedE2e', 'workers'],\n kinds: ['code'],\n tests: true,\n };\n return [\n {\n title: 'no retired Firestore segment survives in a path string',\n description:\n 'A segment name inside a string is never type-checked, so a retired name keeps addressing a collection that no ' +\n 'longer exists — a trigger that never fires, a read that returns nothing, a fixture nobody reads. Flagged: a ' +\n 'retired name in path position in any string or template (`/name/`, or `/name` closing it), and a retired name ' +\n \"as a collectionGroup id. Fix: build the path through the core. A reviewed match names a literal's text that \" +\n 'is not a Firestore path, with its why; one that matches nothing is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const used = new Set<number>();\n for (const file of context.files(scope)) {\n for (const hit of retiredSegmentHits(file, names)) {\n const index = facts.reviewedMatches.findIndex(\n (r) => r.file === file.path && hit.text.includes(r.contains),\n );\n if (index !== -1) {\n used.add(index);\n continue;\n }\n findings.push({\n file: file.path,\n line: lineOfNode(file, hit.node),\n detail: `retired segment '${hit.name}' (now '${replacement.get(hit.name)}') in ${hit.text.slice(0, 80)}`,\n });\n }\n }\n facts.reviewedMatches.forEach((r, i) => {\n if (!used.has(i))\n findings.push({\n file: r.file,\n detail: `stale reviewed match: no retired segment in a literal containing \"${r.contains}\"`,\n });\n });\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"path-guards.js","sourceRoot":"","sources":["../../../src/modules/firestore/path-guards.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { Finding } from '../../guard.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { FileView } from '../../reader/view.js';\nimport { calleeName, forEachNode, isPlainString, lineOfNode, unwrap } from '../../support/ast.js';\nimport { namesProblem, reviewedProblems, takesNoFacts, isList } from '../../support/facts.js';\nimport { escapeRegExp } from '../../support/text.js';\nimport {\n bannedGuard,\n findingsOf,\n isMemberOf,\n literalBindings,\n type NoFacts,\n type NodeHit,\n} from './support.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\nimport { routePrefixPattern, routePrefixProblems } from '../core-definitions/app-routes.js';\n\nconst ACCESSORS = new Set(['collection', 'collectionGroup', 'doc']);\n\nconst APP_CODE: Scope = {\n roots: ['app', 'functions'],\n optionalRoots: ['functions'],\n kinds: ['code'],\n tests: false,\n};\n\nfunction isHandle(node: ts.Expression): boolean {\n const plain = (n: ts.Expression): boolean =>\n ts.isIdentifier(n) || (ts.isPropertyAccessExpression(n) && plain(n.expression));\n return (\n plain(node) ||\n (ts.isCallExpression(node) && node.arguments.length === 0 && plain(node.expression))\n );\n}\n\nfunction accessorPathArguments(\n call: ts.CallExpression,\n): { form: 'method' | 'modular'; args: readonly ts.Expression[] } | null {\n const callee = call.expression;\n if (ts.isPropertyAccessExpression(callee) && ACCESSORS.has(callee.name.text))\n return { form: 'method', args: call.arguments };\n if (\n ts.isIdentifier(callee) &&\n ACCESSORS.has(callee.text) &&\n call.arguments.length >= 2 &&\n isHandle(call.arguments[0])\n ) {\n return { form: 'modular', args: call.arguments.slice(1) };\n }\n return null;\n}\n\nexport function literalSegmentHits(file: FileView): NodeHit[] {\n const source = file.sourceFile;\n const bindings = literalBindings(source);\n const typed = (arg: ts.Expression): string | null => {\n const value = unwrap(arg);\n if (isPlainString(value)) return `'${value.text}'`;\n if (ts.isIdentifier(value) && bindings.has(value.text))\n return `${value.text} (bound to '${bindings.get(value.text)}')`;\n return null;\n };\n const hits: NodeHit[] = [];\n forEachNode(source, (node) => {\n if (!ts.isCallExpression(node)) return;\n const accessor = accessorPathArguments(node);\n if (!accessor) return;\n const name = calleeName(node);\n if (accessor.form === 'method') {\n // The Admin SDK accessor takes the whole segment as its one argument.\n if (accessor.args.length !== 1) return;\n const segment = typed(accessor.args[0]);\n if (segment)\n hits.push({\n node: (node.expression as ts.PropertyAccessExpression).name,\n detail: `.${name}(${segment})`,\n });\n return;\n }\n const segments = accessor.args\n .filter((arg) => !ts.isSpreadElement(arg))\n .map(typed)\n .filter((s): s is string => s !== null);\n if (segments.length) hits.push({ node, detail: `${name}(db, …) with ${segments.join(', ')}` });\n });\n return hits;\n}\n\nexport const literalSegmentGuard: GenericGuardDefinition<NoFacts> = {\n id: 'firestoreLiteralSegments',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems: takesNoFacts,\n build() {\n return [\n bannedGuard({\n title: 'no typed path segment reaches a Firestore accessor',\n description:\n 'A collection or document segment comes from the core package, never a string typed at the call. Flagged: a ' +\n 'string literal (or a name bound to one in the same file) as the whole argument of x.collection / ' +\n 'x.collectionGroup / x.doc, on every link of a chain, and in ANY path position after the handle of the ' +\n 'modular collection(db, …) / collectionGroup(db, …) / doc(db, …). A literal whose text equals the constant is ' +\n \"still the violation. Fix: import the owner — the core's collection constant, or its path builder.\",\n scope: APP_CODE,\n match: literalSegmentHits,\n }),\n ];\n },\n};\n\nexport interface PathAssemblyFacts {\n ownerConstants: readonly string[];\n segmentArrayJoiners: readonly string[];\n reviewedSites: readonly Reviewed[];\n}\n\nconst hasSlash = (node: ts.Expression) => isPlainString(node) && node.text.includes('/');\n\nfunction concatenationOperands(node: ts.Expression): ts.Expression[] {\n const value = unwrap(node);\n if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.PlusToken) {\n return [...concatenationOperands(value.left), ...concatenationOperands(value.right)];\n }\n return [value];\n}\n\nexport function pathAssemblyHits(\n file: FileView,\n facts: Pick<PathAssemblyFacts, 'ownerConstants' | 'segmentArrayJoiners'>,\n): NodeHit[] {\n const owners = new Set(facts.ownerConstants);\n const joiners = new Set(facts.segmentArrayJoiners);\n const isOwner = (node: ts.Expression) => isMemberOf(unwrap(node), owners);\n const hits = new Map<number, NodeHit>();\n const hit = (node: ts.Node, detail: string) => {\n const at = node.getStart(file.sourceFile);\n if (!hits.has(at)) hits.set(at, { node, detail });\n };\n forEachNode(file.sourceFile, (node) => {\n if (ts.isCallExpression(node)) {\n const accessor = accessorPathArguments(node);\n for (const raw of accessor?.args ?? []) {\n const arg = unwrap(raw);\n if (ts.isTemplateExpression(arg)) {\n if (arg.templateSpans.some((span) => isOwner(span.expression)))\n hit(arg, 'an owner constant spliced into a template path');\n else if (\n (arg.head.text + arg.templateSpans.map((s) => s.literal.text).join('')).includes('/')\n ) {\n hit(arg, 'a template path built from a `/` and a hole');\n }\n } else if (ts.isBinaryExpression(arg) && concatenationOperands(arg).some(hasSlash)) {\n hit(arg, 'a slash path concatenated at the accessor');\n }\n }\n const name = calleeName(node);\n if (name && joiners.has(name)) {\n for (const arg of node.arguments) {\n const value = unwrap(arg);\n if (\n ts.isArrayLiteralExpression(value) &&\n value.elements.some((e) => !ts.isSpreadElement(e) && isOwner(e))\n ) {\n hit(value, `a segment array handed to ${name}() with an owner constant in it`);\n }\n }\n }\n } else if (ts.isTemplateExpression(node)) {\n node.templateSpans.forEach((span, i) => {\n const followedByHole = i < node.templateSpans.length - 1;\n if (isOwner(span.expression) && span.literal.text === '/' && followedByHole) {\n hit(node, 'an owner constant joined to a hole with a `/`');\n }\n });\n } else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {\n const left = unwrap(node.left);\n const right = unwrap(node.right);\n if ((isOwner(left) && hasSlash(right)) || (hasSlash(left) && isOwner(right)))\n hit(node, 'an owner constant concatenated with a slash');\n }\n });\n return [...hits.values()].sort(\n (a, b) => a.node.getStart(file.sourceFile) - b.node.getStart(file.sourceFile),\n );\n}\n\nexport const pathAssemblyGuard: GenericGuardDefinition<PathAssemblyFacts> = {\n id: 'firestorePathAssembly',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.ownerConstants, 'ownerConstants', problems);\n namesProblem(facts?.segmentArrayJoiners, 'segmentArrayJoiners', problems, { nonEmpty: false });\n reviewedProblems(facts?.reviewedSites, 'reviewedSites', problems);\n return problems;\n },\n build(facts) {\n const reviewed = new Map(facts.reviewedSites.map((s) => [s.path, s]));\n return [\n {\n title: 'no Firestore path is assembled by hand',\n description:\n \"A path's string form derives from the core's path builder; writing it again at the call is a second spelling. \" +\n 'Flagged: an owner constant spliced into an accessor template; an accessor template holding both a `/` and a ' +\n 'hole, in either order; a slash-bearing literal concatenated in an accessor argument; an owner constant joined ' +\n 'to a hole by a `/` in any template, or concatenated with a slash-bearing literal anywhere; a segment array ' +\n \"holding an owner constant handed to a path joiner. Fix: call the core's path builder. A reviewed site is a file \" +\n 'with its why; an entry whose file no longer assembles a path is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const held = new Set<string>();\n for (const file of context.files(APP_CODE)) {\n const hits = pathAssemblyHits(file, facts);\n if (!hits.length) continue;\n if (reviewed.has(file.path)) held.add(file.path);\n else findings.push(...findingsOf(file, hits));\n }\n for (const path of reviewed.keys()) {\n if (!held.has(path))\n findings.push({\n file: path,\n detail: 'stale reviewed site: the file no longer assembles a path (or is gone)',\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n\nexport interface RetiredSegmentFacts {\n segments: ReadonlyArray<{ name: string; replacement: string }>;\n routePrefixes: readonly string[];\n reviewedMatches: ReadonlyArray<{ file: string; contains: string; why: string }>;\n}\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\n\nexport function retiredSegmentHits(\n file: FileView,\n names: readonly string[],\n routePrefixes: readonly string[],\n): Array<NodeHit & { name: string; text: string }> {\n const source = file.sourceFile;\n const retired = new Set(names);\n const alternation = names.map(escapeRegExp).join('|');\n const inside = new RegExp(`/(${alternation})(?=/)`, 'g');\n const closing = new RegExp(`/(${alternation})$`);\n const routes = routePrefixes.map(routePrefixPattern);\n const specifiers = new Set<ts.Node>(file.imports.map((site) => site.specifier));\n const hits: Array<NodeHit & { name: string; text: string }> = [];\n const scan = (node: ts.Node, parts: Array<{ text: string; closes: boolean }>) => {\n const opening = parts[0].text;\n const whole = parts.map((p) => p.text).join('');\n if (\n specifiers.has(node) ||\n routes.some((route) => route.test(opening)) ||\n SOURCE_FILE.test(whole)\n )\n return;\n const found: string[] = [];\n for (const { text, closes } of parts) {\n for (const m of text.matchAll(inside)) found.push(m[1]);\n const end = closes ? closing.exec(text) : null;\n if (end) found.push(end[1]);\n }\n for (const name of found) hits.push({ node, name, text: node.getText(source), detail: '' });\n };\n forEachNode(source, (node) => {\n if (isPlainString(node)) scan(node, [{ text: node.text, closes: true }]);\n else if (ts.isTemplateExpression(node)) {\n scan(node, [\n { text: node.head.text, closes: false },\n ...node.templateSpans.map((s, i) => ({\n text: s.literal.text,\n closes: i === node.templateSpans.length - 1,\n })),\n ]);\n }\n if (ts.isCallExpression(node) && calleeName(node) === 'collectionGroup') {\n for (const arg of node.arguments) {\n if (isPlainString(arg) && retired.has(arg.text))\n hits.push({ node: arg, name: arg.text, text: arg.getText(source), detail: '' });\n }\n }\n });\n return hits;\n}\n\nexport const retiredSegmentGuard: GenericGuardDefinition<RetiredSegmentFacts> = {\n id: 'firestoreRetiredSegments',\n absentWhen: 'the app has retired no Firestore segment name',\n factProblems(facts) {\n const problems: string[] = [];\n reviewedProblems(facts?.reviewedMatches, 'reviewedMatches', problems, {\n key: 'file',\n id: (e) =>\n typeof e.contains === 'string' && e.contains ? `${e.file} ${e.contains}` : undefined,\n idNames: 'text it contains',\n });\n namesProblem(facts?.routePrefixes, 'routePrefixes', problems, { nonEmpty: false });\n routePrefixProblems(facts?.routePrefixes, 'routePrefixes', problems);\n if (!isList(facts?.segments) || !facts.segments.length)\n return [...problems, 'segments is empty (declare the guard absent instead)'];\n const names = facts.segments.map((s) => s?.name);\n for (const s of facts.segments) {\n if (typeof s?.name !== 'string' || !/^[\\w$-]+$/.test(s.name))\n problems.push(`segments: ${String(s?.name)} is not a segment name`);\n if (typeof s?.replacement !== 'string' || !s.replacement.trim())\n problems.push(`segments: ${String(s?.name)} names no replacement`);\n }\n if (new Set(names).size !== names.length) problems.push('segments names one entry twice');\n return problems;\n },\n build(facts) {\n const replacement = new Map(facts.segments.map((s) => [s.name, s.replacement]));\n const names = [...replacement.keys()];\n const scope: Scope = {\n roots: ['app', 'functions', 'scripts', 'e2e', 'hostedE2e', 'workers'],\n optionalRoots: ['functions', 'scripts', 'e2e', 'hostedE2e', 'workers'],\n kinds: ['code'],\n tests: true,\n };\n return [\n {\n title: 'no retired Firestore segment survives in a path string',\n description:\n 'A segment name inside a string is never type-checked, so a retired name keeps addressing a collection that no ' +\n 'longer exists — a trigger that never fires, a read that returns nothing, a fixture nobody reads. Flagged: a ' +\n 'retired name in path position in any string or template (`/name/`, or `/name` closing it), and a retired name ' +\n 'as a collectionGroup id. Never flagged: a module specifier (an import, re-export, `import()`, `require()`, ' +\n 'test-runner mock, or type import), a string or template that opens with one of the app page roots ' +\n `(${facts.routePrefixes.join(', ') || 'none declared'}) as a whole segment, and a string or template that ` +\n 'names a source file (it ends in a .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, or .cts extension). Fix: build the path ' +\n \"through the core. A reviewed match names a literal's text that is not a Firestore path, with its why; one that \" +\n 'matches nothing is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const used = new Set<number>();\n for (const file of context.files(scope)) {\n for (const hit of retiredSegmentHits(file, names, facts.routePrefixes)) {\n const index = facts.reviewedMatches.findIndex(\n (r) => r.file === file.path && hit.text.includes(r.contains),\n );\n if (index !== -1) {\n used.add(index);\n continue;\n }\n findings.push({\n file: file.path,\n line: lineOfNode(file, hit.node),\n detail: `retired segment '${hit.name}' (now '${replacement.get(hit.name)}') in ${hit.text.slice(0, 80)}`,\n });\n }\n }\n facts.reviewedMatches.forEach((r, i) => {\n if (!used.has(i))\n findings.push({\n file: r.file,\n detail: `stale reviewed match: no retired segment in a literal containing \"${r.contains}\"`,\n });\n });\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
@@ -2,6 +2,7 @@ import type { GenericGuardDefinition } from '../../node/suite/config.js';
|
|
|
2
2
|
import type { FileView } from '../../reader/view.js';
|
|
3
3
|
import { type AllowlistEntry, type Hit } from '../../shapes/shapes.js';
|
|
4
4
|
export interface SharedTruncationFacts {
|
|
5
|
+
entryStylesheet: string;
|
|
5
6
|
trees: readonly string[];
|
|
6
7
|
sharedClass: string;
|
|
7
8
|
clampOwners: ReadonlyArray<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"truncation.d.ts","sourceRoot":"","sources":["../../../src/modules/ui/truncation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"truncation.d.ts","sourceRoot":"","sources":["../../../src/modules/ui/truncation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAqB1F,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;CACvC;AAID,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,OACS,CAAC;AAsB5D,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,eAAe,EAAE,CA4BlE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,CAcrD;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,OAAO,GAAG,aAAa,CAAC,GAC1D,GAAG,EAAE,CAQP;AAED,eAAO,MAAM,qBAAqB,EAAE,sBAAsB,CAAC,qBAAqB,CAoG/E,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { forEachNode } from '../../support/ast.js';
|
|
|
4
4
|
import { allowlistProblems, isText, pathProblem, reviewedProblems, textProblem, isList, } from '../../support/facts.js';
|
|
5
5
|
import { isUnder } from '../../support/text.js';
|
|
6
6
|
import { cssBlocks, selectorsOf, splitTopLevel } from './css.js';
|
|
7
|
+
import { loadedStylesheets } from './stylesheets.js';
|
|
7
8
|
import { bareToken, codeStrings, tokensOf } from './source.js';
|
|
8
9
|
const APP_CODE_AND_STYLES = { roots: ['app'], kinds: ['code', 'stylesheet'], tests: false };
|
|
9
10
|
const TRUNCATION_TOKEN = /^(?:truncate|text-ellipsis|line-clamp-(?:\d+|none|\[[^\]]+\]))$/;
|
|
@@ -93,6 +94,7 @@ export const sharedTruncationGuard = {
|
|
|
93
94
|
absentWhen: false,
|
|
94
95
|
factProblems(facts) {
|
|
95
96
|
const problems = [];
|
|
97
|
+
pathProblem(facts?.entryStylesheet, 'entryStylesheet', problems);
|
|
96
98
|
if (!isList(facts?.trees) || !facts.trees.length)
|
|
97
99
|
problems.push('trees is missing');
|
|
98
100
|
else
|
|
@@ -122,7 +124,9 @@ export const sharedTruncationGuard = {
|
|
|
122
124
|
'`WebkitLineClamp`, or `lineClamp` style. A stylesheet sets `text-overflow: ellipsis`, `-webkit-line-clamp`, or ' +
|
|
123
125
|
'`line-clamp` (or applies one of ' +
|
|
124
126
|
`those utilities) only in a rule whose every selector is a clamp owner (${facts.clampOwners.map((o) => o.selector).join(', ')}). ` +
|
|
125
|
-
|
|
127
|
+
`A clamp owner is declared by a stylesheet ${facts.entryStylesheet} loads — the theme package's or the app's — ` +
|
|
128
|
+
'and one no loaded stylesheet declares as a clamp any more is stale. Use the shared class, or add a clamp owner ' +
|
|
129
|
+
'or a reviewed exception with its why.',
|
|
126
130
|
scope: APP_CODE_AND_STYLES,
|
|
127
131
|
match: (file) => truncationHits(file, facts),
|
|
128
132
|
allowlist: facts.exceptions,
|
|
@@ -166,8 +170,10 @@ export const sharedTruncationGuard = {
|
|
|
166
170
|
file: t,
|
|
167
171
|
detail: 'the tree holds no production code or stylesheet the guard can read',
|
|
168
172
|
});
|
|
173
|
+
const loaded = loadedStylesheets(context, facts.entryStylesheet);
|
|
174
|
+
findings.push(...loaded.findings);
|
|
169
175
|
const held = new Set();
|
|
170
|
-
for (const file of files)
|
|
176
|
+
for (const file of [...loaded.sheets.map((sheet) => sheet.file), ...files])
|
|
171
177
|
if (file.kind === 'stylesheet')
|
|
172
178
|
for (const clamp of stylesheetClamps(file))
|
|
173
179
|
for (const s of clamp.selectors)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"truncation.js","sourceRoot":"","sources":["../../../src/modules/ui/truncation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAK5B,OAAO,EAAE,iBAAiB,EAAiC,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,MAAM,GACP,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,mBAAmB,GAAU,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAEnG,MAAM,gBAAgB,GAAG,iEAAiE,CAAC;AAC3F,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,iBAAiB,EAAE,WAAW,CAAC,CAAC,CAAC;AACpF,MAAM,KAAK,GAAG,qBAAqB,CAAC;AASpC,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAElE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAa,EAAW,EAAE,CAC1D,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;AAE5D,SAAS,kBAAkB,CAAC,IAAY,EAAE,EAAU;IAClD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,KAAK,IAAI,CAAC,CAAC;aAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACzB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,IAAI,GAAG,CAAC,CAAC;gBACT,MAAM;YACR,CAAC;YACD,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC;IACrB,OAAO,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAE,KAAK,IAAI,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IACpD,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAChF,CAAC;AAQD,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,MAAM;YAAE,SAAS;QAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACnC,MAAM,MAAM,GACV,CAAC,CAAC,CAAC,QAAQ,KAAK,eAAe,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBAChE,CAAC,CAAC,QAAQ,KAAK,oBAAoB;gBACnC,CAAC,CAAC,QAAQ,KAAK,YAAY,CAAC;YAC9B,IAAI,MAAM;gBACR,GAAG,CAAC,IAAI,CAAC;oBACP,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;oBACzC,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,KAAK,EAAE;iBACpC,CAAC,CAAC;QACP,CAAC;IACH,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjD,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACxD,IAAI,MAAM,CAAC,MAAM;YACf,GAAG,CAAC,IAAI,CAAC;gBACP,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC;gBACjD,MAAM,EAAE,UAAU,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;aACrC,CAAC,CAAC;IACP,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAc;IAC5C,MAAM,IAAI,GAAU,EAAE,CAAC;IACvB,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC;QAC7C,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC;YAChC,IAAI,iBAAiB,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACpC,IACE,CAAC,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,6BAA6B,CAAC,IAAI,CAAC,CAAC;YACzE,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7D,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAErC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,IAAc,EACd,KAA2D;IAE3D,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACrE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,gBAAgB,CAAC,IAAI,CAAC;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SAC9E,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAkD;IAClF,EAAE,EAAE,kBAAkB;IACtB,UAAU,EAAE,KAAK;IACjB,YAAY,CAAC,KAAK;QAChB,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM;YAAE,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;;YAC/E,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC5E,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC;YACpE,QAAQ,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,WAAW,oCAAoC,CAAC,CAAC;QACtF,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE;YAC5D,GAAG,EAAE,UAAU;YACf,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QACH,IACE,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC;YAC1B,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC;YAC1B,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YAEvF,QAAQ,CAAC,IAAI,CAAC,0DAA0D,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/F,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC5C,MAAM,KAAK,GAAG,iBAAiB,CAAC;YAC9B,KAAK,EAAE,wDAAwD;YAC/D,WAAW,EACT,SAAS,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oDAAoD,KAAK,CAAC,WAAW,WAAW;gBAC/G,+GAA+G;gBAC/G,6GAA6G;gBAC7G,iHAAiH;gBACjH,kCAAkC;gBAClC,0EAA0E,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;gBAClI,kFAAkF;YACpF,KAAK,EAAE,mBAAmB;YAC1B,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC;YAC5C,SAAS,EAAE,KAAK,CAAC,UAAU;YAC3B,OAAO,EAAE;gBACP,KAAK,EAAE;oBACL,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,iDAAiD,EAAE;oBACvF,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,uCAAuC,EAAE;oBAC7E;wBACE,IAAI,EAAE,GAAG,IAAI,aAAa;wBAC1B,IAAI,EAAE,8DAA8D;qBACrE;oBACD,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,4CAA4C,EAAE;oBAClF,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,0CAA0C,EAAE;oBAChF,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,sCAAsC,EAAE;iBAC7E;gBACD,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,GAAG,IAAI,aAAa;wBAC1B,IAAI,EAAE,4BAA4B,KAAK,CAAC,WAAW,gBAAgB;qBACpE;oBACD;wBACE,IAAI,EAAE,GAAG,IAAI,aAAa;wBAC1B,IAAI,EAAE,8DAA8D;qBACrE;oBACD,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,GAAG,KAAK,+BAA+B,EAAE;oBAC7E,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,gCAAgC,EAAE;iBACvE;aACF;SACF,CAAC,CAAC;QACH,OAAO;YACL;gBACE,GAAG,KAAK;gBACR,KAAK,CAAC,GAAG,CAAC,OAAO;oBACf,MAAM,QAAQ,GAAc,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;oBAC5D,MAAM,KAAK,GAAG,OAAO;yBAClB,KAAK,CAAC,mBAAmB,CAAC;yBAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC9D,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK;wBACzB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;4BACxC,QAAQ,CAAC,IAAI,CAAC;gCACZ,IAAI,EAAE,CAAC;gCACP,MAAM,EAAE,oEAAoE;6BAC7E,CAAC,CAAC;oBACP,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;oBAC/B,KAAK,MAAM,IAAI,IAAI,KAAK;wBACtB,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;4BAC5B,KAAK,MAAM,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC;gCACxC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS;oCAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACnD,KAAK,MAAM,EAAE,QAAQ,EAAE,IAAI,KAAK,CAAC,WAAW;wBAC1C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;4BAC7B,QAAQ,CAAC,IAAI,CAAC;gCACZ,IAAI,EAAE,eAAe;gCACrB,MAAM,EAAE,6CAA6C,QAAQ,qBAAqB;6BACnF,CAAC,CAAC;oBACP,OAAO,QAAQ,CAAC;gBAClB,CAAC;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding } from '../../guard.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { FileView } from '../../reader/view.js';\nimport { reviewedAllowlist, type AllowlistEntry, type Hit } from '../../shapes/shapes.js';\nimport { forEachNode } from '../../support/ast.js';\nimport {\n allowlistProblems,\n isText,\n pathProblem,\n reviewedProblems,\n textProblem,\n isList,\n} from '../../support/facts.js';\nimport { isUnder } from '../../support/text.js';\nimport { cssBlocks, selectorsOf, splitTopLevel } from './css.js';\nimport { bareToken, codeStrings, tokensOf } from './source.js';\n\nconst APP_CODE_AND_STYLES: Scope = { roots: ['app'], kinds: ['code', 'stylesheet'], tests: false };\n\nconst TRUNCATION_TOKEN = /^(?:truncate|text-ellipsis|line-clamp-(?:\\d+|none|\\[[^\\]]+\\]))$/;\nconst INLINE_STYLE_KEYS = new Set(['textOverflow', 'WebkitLineClamp', 'lineClamp']);\nconst APPLY = /@apply\\s+([^;{}]+)/g;\n\nexport interface SharedTruncationFacts {\n trees: readonly string[];\n sharedClass: string;\n clampOwners: ReadonlyArray<{ selector: string; why: string }>;\n exceptions: readonly AllowlistEntry[];\n}\n\nconst squash = (text: string) => text.replace(/\\s+/g, ' ').trim();\n\nexport const isTruncationToken = (token: string): boolean =>\n TRUNCATION_TOKEN.test(bareToken(token).replace(/^!/, ''));\n\nfunction enclosingSelectors(code: string, at: number): string[] {\n let depth = 0;\n let open = -1;\n for (let i = at - 1; i >= 0; i -= 1) {\n if (code[i] === '}') depth += 1;\n else if (code[i] === '{') {\n if (depth === 0) {\n open = i;\n break;\n }\n depth -= 1;\n }\n }\n if (open === -1) return [];\n let start = open - 1;\n while (start >= 0 && !'{};'.includes(code[start])) start -= 1;\n const prelude = squash(code.slice(start + 1, open));\n return prelude.startsWith('@') ? [] : splitTopLevel(prelude, ',').map(squash);\n}\n\ninterface StylesheetClamp {\n index: number;\n selectors: string[];\n detail: string;\n}\n\nexport function stylesheetClamps(file: FileView): StylesheetClamp[] {\n const out: StylesheetClamp[] = [];\n for (const block of cssBlocks(file.code)) {\n if (block.atRule) continue;\n for (const d of block.declarations) {\n const clamps =\n (d.property === 'text-overflow' && /\\bellipsis\\b/.test(d.value)) ||\n d.property === '-webkit-line-clamp' ||\n d.property === 'line-clamp';\n if (clamps)\n out.push({\n index: d.index,\n selectors: selectorsOf(block).map(squash),\n detail: `${d.property}: ${d.value}`,\n });\n }\n }\n const re = new RegExp(APPLY.source, APPLY.flags);\n for (let m = re.exec(file.code); m; m = re.exec(file.code)) {\n const tokens = tokensOf(m[1]).filter(isTruncationToken);\n if (tokens.length)\n out.push({\n index: m.index,\n selectors: enclosingSelectors(file.code, m.index),\n detail: `@apply ${tokens.join(' ')}`,\n });\n }\n return out.sort((a, b) => a.index - b.index);\n}\n\nexport function codeTruncations(file: FileView): Hit[] {\n const hits: Hit[] = [];\n for (const { text, index } of codeStrings(file))\n for (const token of tokensOf(text))\n if (isTruncationToken(token)) hits.push({ index, detail: token });\n forEachNode(file.sourceFile, (node) => {\n if (\n (ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node)) &&\n (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) &&\n INLINE_STYLE_KEYS.has(node.name.text)\n )\n hits.push({ index: node.getStart(file.sourceFile), detail: `inline ${node.name.text}` });\n });\n return hits.sort((a, b) => a.index - b.index);\n}\n\nexport function truncationHits(\n file: FileView,\n facts: Pick<SharedTruncationFacts, 'trees' | 'clampOwners'>,\n): Hit[] {\n if (!facts.trees.some((tree) => isUnder(file.path, tree))) return [];\n if (file.kind === 'code') return codeTruncations(file);\n if (file.kind !== 'stylesheet') return [];\n const owners = new Set(facts.clampOwners.map((o) => squash(o.selector)));\n return stylesheetClamps(file)\n .filter((c) => !c.selectors.length || !c.selectors.every((s) => owners.has(s)))\n .map((c) => ({ index: c.index, detail: c.detail }));\n}\n\nexport const sharedTruncationGuard: GenericGuardDefinition<SharedTruncationFacts> = {\n id: 'sharedTruncation',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!isList(facts?.trees) || !facts.trees.length) problems.push('trees is missing');\n else facts.trees.forEach((t, i) => pathProblem(t, `trees[${i}]`, problems));\n textProblem(facts?.sharedClass, 'sharedClass', problems);\n if (isText(facts?.sharedClass) && isTruncationToken(facts.sharedClass))\n problems.push(`sharedClass ${facts.sharedClass} is itself a per-call-site utility`);\n reviewedProblems(facts?.clampOwners, 'clampOwners', problems, {\n key: 'selector',\n nonEmpty: true,\n });\n if (\n isList(facts?.clampOwners) &&\n isText(facts?.sharedClass) &&\n !facts.clampOwners.some((o) => squash(String(o?.selector)) === `.${facts.sharedClass}`)\n )\n problems.push(`clampOwners does not hold the shared class's selector .${facts.sharedClass}`);\n allowlistProblems(facts?.exceptions, 'exceptions', problems);\n return problems;\n },\n build(facts) {\n const tree = facts.trees[0];\n const owner = facts.clampOwners[0].selector;\n const guard = reviewedAllowlist({\n title: 'titles and names truncate through the one shared class',\n description:\n `Under ${facts.trees.join(', ')}, text truncates only through the shared class \\`${facts.sharedClass}\\`. Code ` +\n 'never names a per-call-site truncation utility (`truncate`, `line-clamp-<n|none|[…]>`, `text-ellipsis`, with ' +\n 'or without a variant prefix) in a string, template, or className, and never sets an inline `textOverflow`, ' +\n '`WebkitLineClamp`, or `lineClamp` style. A stylesheet sets `text-overflow: ellipsis`, `-webkit-line-clamp`, or ' +\n '`line-clamp` (or applies one of ' +\n `those utilities) only in a rule whose every selector is a clamp owner (${facts.clampOwners.map((o) => o.selector).join(', ')}). ` +\n 'Use the shared class, or add a clamp owner or a reviewed exception with its why.',\n scope: APP_CODE_AND_STYLES,\n match: (file) => truncationHits(file, facts),\n allowlist: facts.exceptions,\n samples: {\n flags: [\n { path: `${tree}/sample.tsx`, text: '<h2 className=\"font-bold truncate\">{title}</h2>' },\n { path: `${tree}/sample.tsx`, text: 'const c = cn(`md:line-clamp-2 ${x}`);' },\n {\n path: `${tree}/sample.tsx`,\n text: \"<p className={open ? 'line-clamp-none' : 'text-ellipsis'} />\",\n },\n { path: `${tree}/sample.tsx`, text: \"<p style={{ textOverflow: 'ellipsis' }} />\" },\n { path: `${tree}/sample.css`, text: '.card-title { text-overflow: ellipsis; }' },\n { path: `${tree}/sample.css`, text: '.card-title { @apply line-clamp-3; }' },\n ],\n passes: [\n {\n path: `${tree}/sample.tsx`,\n text: `<h2 className=\"font-bold ${facts.sharedClass}\">{title}</h2>`,\n },\n {\n path: `${tree}/sample.tsx`,\n text: '// a truncate class would go here\\nconst mode = \"truncated\";',\n },\n { path: `${tree}/sample.css`, text: `${owner} { text-overflow: ellipsis; }` },\n { path: `${tree}/sample.css`, text: '.card { text-overflow: clip; }' },\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings: Finding[] = [...(await guard.run(context))];\n const files = context\n .files(APP_CODE_AND_STYLES)\n .filter((f) => facts.trees.some((t) => isUnder(f.path, t)));\n for (const t of facts.trees)\n if (!files.some((f) => isUnder(f.path, t)))\n findings.push({\n file: t,\n detail: 'the tree holds no production code or stylesheet the guard can read',\n });\n const held = new Set<string>();\n for (const file of files)\n if (file.kind === 'stylesheet')\n for (const clamp of stylesheetClamps(file))\n for (const s of clamp.selectors) held.add(s);\n for (const { selector } of facts.clampOwners)\n if (!held.has(squash(selector)))\n findings.push({\n file: '(clampOwners)',\n detail: `stale clamp owner: no stylesheet rule for ${selector} truncates any more`,\n });\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"truncation.js","sourceRoot":"","sources":["../../../src/modules/ui/truncation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAK5B,OAAO,EAAE,iBAAiB,EAAiC,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,MAAM,GACP,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,mBAAmB,GAAU,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAEnG,MAAM,gBAAgB,GAAG,iEAAiE,CAAC;AAC3F,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,iBAAiB,EAAE,WAAW,CAAC,CAAC,CAAC;AACpF,MAAM,KAAK,GAAG,qBAAqB,CAAC;AAUpC,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAElE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAa,EAAW,EAAE,CAC1D,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;AAE5D,SAAS,kBAAkB,CAAC,IAAY,EAAE,EAAU;IAClD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,KAAK,IAAI,CAAC,CAAC;aAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACzB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,IAAI,GAAG,CAAC,CAAC;gBACT,MAAM;YACR,CAAC;YACD,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC;IACrB,OAAO,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAE,KAAK,IAAI,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IACpD,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAChF,CAAC;AAQD,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,MAAM;YAAE,SAAS;QAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACnC,MAAM,MAAM,GACV,CAAC,CAAC,CAAC,QAAQ,KAAK,eAAe,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBAChE,CAAC,CAAC,QAAQ,KAAK,oBAAoB;gBACnC,CAAC,CAAC,QAAQ,KAAK,YAAY,CAAC;YAC9B,IAAI,MAAM;gBACR,GAAG,CAAC,IAAI,CAAC;oBACP,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;oBACzC,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,KAAK,EAAE;iBACpC,CAAC,CAAC;QACP,CAAC;IACH,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjD,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACxD,IAAI,MAAM,CAAC,MAAM;YACf,GAAG,CAAC,IAAI,CAAC;gBACP,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC;gBACjD,MAAM,EAAE,UAAU,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;aACrC,CAAC,CAAC;IACP,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAc;IAC5C,MAAM,IAAI,GAAU,EAAE,CAAC;IACvB,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC;QAC7C,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC;YAChC,IAAI,iBAAiB,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;QACpC,IACE,CAAC,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,6BAA6B,CAAC,IAAI,CAAC,CAAC;YACzE,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7D,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAErC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,IAAc,EACd,KAA2D;IAE3D,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACrE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,gBAAgB,CAAC,IAAI,CAAC;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SAC9E,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAkD;IAClF,EAAE,EAAE,kBAAkB;IACtB,UAAU,EAAE,KAAK;IACjB,YAAY,CAAC,KAAK;QAChB,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,QAAQ,CAAC,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM;YAAE,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;;YAC/E,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC5E,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC;YACpE,QAAQ,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,WAAW,oCAAoC,CAAC,CAAC;QACtF,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE;YAC5D,GAAG,EAAE,UAAU;YACf,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QACH,IACE,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC;YAC1B,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC;YAC1B,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YAEvF,QAAQ,CAAC,IAAI,CAAC,0DAA0D,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/F,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC5C,MAAM,KAAK,GAAG,iBAAiB,CAAC;YAC9B,KAAK,EAAE,wDAAwD;YAC/D,WAAW,EACT,SAAS,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oDAAoD,KAAK,CAAC,WAAW,WAAW;gBAC/G,+GAA+G;gBAC/G,6GAA6G;gBAC7G,iHAAiH;gBACjH,kCAAkC;gBAClC,0EAA0E,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;gBAClI,6CAA6C,KAAK,CAAC,eAAe,8CAA8C;gBAChH,iHAAiH;gBACjH,uCAAuC;YACzC,KAAK,EAAE,mBAAmB;YAC1B,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC;YAC5C,SAAS,EAAE,KAAK,CAAC,UAAU;YAC3B,OAAO,EAAE;gBACP,KAAK,EAAE;oBACL,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,iDAAiD,EAAE;oBACvF,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,uCAAuC,EAAE;oBAC7E;wBACE,IAAI,EAAE,GAAG,IAAI,aAAa;wBAC1B,IAAI,EAAE,8DAA8D;qBACrE;oBACD,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,4CAA4C,EAAE;oBAClF,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,0CAA0C,EAAE;oBAChF,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,sCAAsC,EAAE;iBAC7E;gBACD,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,GAAG,IAAI,aAAa;wBAC1B,IAAI,EAAE,4BAA4B,KAAK,CAAC,WAAW,gBAAgB;qBACpE;oBACD;wBACE,IAAI,EAAE,GAAG,IAAI,aAAa;wBAC1B,IAAI,EAAE,8DAA8D;qBACrE;oBACD,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,GAAG,KAAK,+BAA+B,EAAE;oBAC7E,EAAE,IAAI,EAAE,GAAG,IAAI,aAAa,EAAE,IAAI,EAAE,gCAAgC,EAAE;iBACvE;aACF;SACF,CAAC,CAAC;QACH,OAAO;YACL;gBACE,GAAG,KAAK;gBACR,KAAK,CAAC,GAAG,CAAC,OAAO;oBACf,MAAM,QAAQ,GAAc,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;oBAC5D,MAAM,KAAK,GAAG,OAAO;yBAClB,KAAK,CAAC,mBAAmB,CAAC;yBAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC9D,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK;wBACzB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;4BACxC,QAAQ,CAAC,IAAI,CAAC;gCACZ,IAAI,EAAE,CAAC;gCACP,MAAM,EAAE,oEAAoE;6BAC7E,CAAC,CAAC;oBACP,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;oBACjE,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;oBAClC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;oBAC/B,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC;wBACxE,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;4BAC5B,KAAK,MAAM,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC;gCACxC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS;oCAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACnD,KAAK,MAAM,EAAE,QAAQ,EAAE,IAAI,KAAK,CAAC,WAAW;wBAC1C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;4BAC7B,QAAQ,CAAC,IAAI,CAAC;gCACZ,IAAI,EAAE,eAAe;gCACrB,MAAM,EAAE,6CAA6C,QAAQ,qBAAqB;6BACnF,CAAC,CAAC;oBACP,OAAO,QAAQ,CAAC;gBAClB,CAAC;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding } from '../../guard.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { FileView } from '../../reader/view.js';\nimport { reviewedAllowlist, type AllowlistEntry, type Hit } from '../../shapes/shapes.js';\nimport { forEachNode } from '../../support/ast.js';\nimport {\n allowlistProblems,\n isText,\n pathProblem,\n reviewedProblems,\n textProblem,\n isList,\n} from '../../support/facts.js';\nimport { isUnder } from '../../support/text.js';\nimport { cssBlocks, selectorsOf, splitTopLevel } from './css.js';\nimport { loadedStylesheets } from './stylesheets.js';\nimport { bareToken, codeStrings, tokensOf } from './source.js';\n\nconst APP_CODE_AND_STYLES: Scope = { roots: ['app'], kinds: ['code', 'stylesheet'], tests: false };\n\nconst TRUNCATION_TOKEN = /^(?:truncate|text-ellipsis|line-clamp-(?:\\d+|none|\\[[^\\]]+\\]))$/;\nconst INLINE_STYLE_KEYS = new Set(['textOverflow', 'WebkitLineClamp', 'lineClamp']);\nconst APPLY = /@apply\\s+([^;{}]+)/g;\n\nexport interface SharedTruncationFacts {\n entryStylesheet: string;\n trees: readonly string[];\n sharedClass: string;\n clampOwners: ReadonlyArray<{ selector: string; why: string }>;\n exceptions: readonly AllowlistEntry[];\n}\n\nconst squash = (text: string) => text.replace(/\\s+/g, ' ').trim();\n\nexport const isTruncationToken = (token: string): boolean =>\n TRUNCATION_TOKEN.test(bareToken(token).replace(/^!/, ''));\n\nfunction enclosingSelectors(code: string, at: number): string[] {\n let depth = 0;\n let open = -1;\n for (let i = at - 1; i >= 0; i -= 1) {\n if (code[i] === '}') depth += 1;\n else if (code[i] === '{') {\n if (depth === 0) {\n open = i;\n break;\n }\n depth -= 1;\n }\n }\n if (open === -1) return [];\n let start = open - 1;\n while (start >= 0 && !'{};'.includes(code[start])) start -= 1;\n const prelude = squash(code.slice(start + 1, open));\n return prelude.startsWith('@') ? [] : splitTopLevel(prelude, ',').map(squash);\n}\n\ninterface StylesheetClamp {\n index: number;\n selectors: string[];\n detail: string;\n}\n\nexport function stylesheetClamps(file: FileView): StylesheetClamp[] {\n const out: StylesheetClamp[] = [];\n for (const block of cssBlocks(file.code)) {\n if (block.atRule) continue;\n for (const d of block.declarations) {\n const clamps =\n (d.property === 'text-overflow' && /\\bellipsis\\b/.test(d.value)) ||\n d.property === '-webkit-line-clamp' ||\n d.property === 'line-clamp';\n if (clamps)\n out.push({\n index: d.index,\n selectors: selectorsOf(block).map(squash),\n detail: `${d.property}: ${d.value}`,\n });\n }\n }\n const re = new RegExp(APPLY.source, APPLY.flags);\n for (let m = re.exec(file.code); m; m = re.exec(file.code)) {\n const tokens = tokensOf(m[1]).filter(isTruncationToken);\n if (tokens.length)\n out.push({\n index: m.index,\n selectors: enclosingSelectors(file.code, m.index),\n detail: `@apply ${tokens.join(' ')}`,\n });\n }\n return out.sort((a, b) => a.index - b.index);\n}\n\nexport function codeTruncations(file: FileView): Hit[] {\n const hits: Hit[] = [];\n for (const { text, index } of codeStrings(file))\n for (const token of tokensOf(text))\n if (isTruncationToken(token)) hits.push({ index, detail: token });\n forEachNode(file.sourceFile, (node) => {\n if (\n (ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node)) &&\n (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) &&\n INLINE_STYLE_KEYS.has(node.name.text)\n )\n hits.push({ index: node.getStart(file.sourceFile), detail: `inline ${node.name.text}` });\n });\n return hits.sort((a, b) => a.index - b.index);\n}\n\nexport function truncationHits(\n file: FileView,\n facts: Pick<SharedTruncationFacts, 'trees' | 'clampOwners'>,\n): Hit[] {\n if (!facts.trees.some((tree) => isUnder(file.path, tree))) return [];\n if (file.kind === 'code') return codeTruncations(file);\n if (file.kind !== 'stylesheet') return [];\n const owners = new Set(facts.clampOwners.map((o) => squash(o.selector)));\n return stylesheetClamps(file)\n .filter((c) => !c.selectors.length || !c.selectors.every((s) => owners.has(s)))\n .map((c) => ({ index: c.index, detail: c.detail }));\n}\n\nexport const sharedTruncationGuard: GenericGuardDefinition<SharedTruncationFacts> = {\n id: 'sharedTruncation',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.entryStylesheet, 'entryStylesheet', problems);\n if (!isList(facts?.trees) || !facts.trees.length) problems.push('trees is missing');\n else facts.trees.forEach((t, i) => pathProblem(t, `trees[${i}]`, problems));\n textProblem(facts?.sharedClass, 'sharedClass', problems);\n if (isText(facts?.sharedClass) && isTruncationToken(facts.sharedClass))\n problems.push(`sharedClass ${facts.sharedClass} is itself a per-call-site utility`);\n reviewedProblems(facts?.clampOwners, 'clampOwners', problems, {\n key: 'selector',\n nonEmpty: true,\n });\n if (\n isList(facts?.clampOwners) &&\n isText(facts?.sharedClass) &&\n !facts.clampOwners.some((o) => squash(String(o?.selector)) === `.${facts.sharedClass}`)\n )\n problems.push(`clampOwners does not hold the shared class's selector .${facts.sharedClass}`);\n allowlistProblems(facts?.exceptions, 'exceptions', problems);\n return problems;\n },\n build(facts) {\n const tree = facts.trees[0];\n const owner = facts.clampOwners[0].selector;\n const guard = reviewedAllowlist({\n title: 'titles and names truncate through the one shared class',\n description:\n `Under ${facts.trees.join(', ')}, text truncates only through the shared class \\`${facts.sharedClass}\\`. Code ` +\n 'never names a per-call-site truncation utility (`truncate`, `line-clamp-<n|none|[…]>`, `text-ellipsis`, with ' +\n 'or without a variant prefix) in a string, template, or className, and never sets an inline `textOverflow`, ' +\n '`WebkitLineClamp`, or `lineClamp` style. A stylesheet sets `text-overflow: ellipsis`, `-webkit-line-clamp`, or ' +\n '`line-clamp` (or applies one of ' +\n `those utilities) only in a rule whose every selector is a clamp owner (${facts.clampOwners.map((o) => o.selector).join(', ')}). ` +\n `A clamp owner is declared by a stylesheet ${facts.entryStylesheet} loads — the theme package's or the app's — ` +\n 'and one no loaded stylesheet declares as a clamp any more is stale. Use the shared class, or add a clamp owner ' +\n 'or a reviewed exception with its why.',\n scope: APP_CODE_AND_STYLES,\n match: (file) => truncationHits(file, facts),\n allowlist: facts.exceptions,\n samples: {\n flags: [\n { path: `${tree}/sample.tsx`, text: '<h2 className=\"font-bold truncate\">{title}</h2>' },\n { path: `${tree}/sample.tsx`, text: 'const c = cn(`md:line-clamp-2 ${x}`);' },\n {\n path: `${tree}/sample.tsx`,\n text: \"<p className={open ? 'line-clamp-none' : 'text-ellipsis'} />\",\n },\n { path: `${tree}/sample.tsx`, text: \"<p style={{ textOverflow: 'ellipsis' }} />\" },\n { path: `${tree}/sample.css`, text: '.card-title { text-overflow: ellipsis; }' },\n { path: `${tree}/sample.css`, text: '.card-title { @apply line-clamp-3; }' },\n ],\n passes: [\n {\n path: `${tree}/sample.tsx`,\n text: `<h2 className=\"font-bold ${facts.sharedClass}\">{title}</h2>`,\n },\n {\n path: `${tree}/sample.tsx`,\n text: '// a truncate class would go here\\nconst mode = \"truncated\";',\n },\n { path: `${tree}/sample.css`, text: `${owner} { text-overflow: ellipsis; }` },\n { path: `${tree}/sample.css`, text: '.card { text-overflow: clip; }' },\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings: Finding[] = [...(await guard.run(context))];\n const files = context\n .files(APP_CODE_AND_STYLES)\n .filter((f) => facts.trees.some((t) => isUnder(f.path, t)));\n for (const t of facts.trees)\n if (!files.some((f) => isUnder(f.path, t)))\n findings.push({\n file: t,\n detail: 'the tree holds no production code or stylesheet the guard can read',\n });\n const loaded = loadedStylesheets(context, facts.entryStylesheet);\n findings.push(...loaded.findings);\n const held = new Set<string>();\n for (const file of [...loaded.sheets.map((sheet) => sheet.file), ...files])\n if (file.kind === 'stylesheet')\n for (const clamp of stylesheetClamps(file))\n for (const s of clamp.selectors) held.add(s);\n for (const { selector } of facts.clampOwners)\n if (!held.has(squash(selector)))\n findings.push({\n file: '(clampOwners)',\n detail: `stale clamp owner: no stylesheet rule for ${selector} truncates any more`,\n });\n return findings;\n },\n },\n ];\n },\n};\n"]}
|