@decantr/verifier 2.5.1 → 3.0.0-next.0
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/README.md +9 -0
- package/dist/index.d.ts +376 -2
- package/dist/index.js +1888 -455
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
- package/schema/evidence-bundle.v1.json +99 -1
- package/schema/project-health-report.v1.json +129 -0
- package/schema/scan-report.v1.json +122 -0
- package/schema/verification-report.common.v1.json +59 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,353 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
|
-
import { existsSync as
|
|
3
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
4
4
|
import { readFile } from "fs/promises";
|
|
5
|
-
import { basename, dirname, extname as
|
|
5
|
+
import { basename as basename2, dirname, extname as extname4, isAbsolute as isAbsolute2, join as join4, relative as relative4, resolve } from "path";
|
|
6
6
|
import { evaluateGuard, isV4, validateEssence } from "@decantr/essence-spec";
|
|
7
|
+
import * as ts3 from "typescript";
|
|
8
|
+
|
|
9
|
+
// src/component-reuse.ts
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
|
+
import { basename, extname, relative } from "path";
|
|
7
12
|
import * as ts from "typescript";
|
|
13
|
+
var COMPONENT_REUSE_RULE_ID = "component-reuse-primitive-reimplemented";
|
|
14
|
+
var RAW_CONTROL_REUSE_RULE_ID = "component-reuse-raw-control";
|
|
15
|
+
var PRIMITIVE_COMPONENT_NAMES = /* @__PURE__ */ new Set([
|
|
16
|
+
"Accordion",
|
|
17
|
+
"Alert",
|
|
18
|
+
"Avatar",
|
|
19
|
+
"Badge",
|
|
20
|
+
"Breadcrumb",
|
|
21
|
+
"Button",
|
|
22
|
+
"Calendar",
|
|
23
|
+
"Card",
|
|
24
|
+
"Chart",
|
|
25
|
+
"Checkbox",
|
|
26
|
+
"Combobox",
|
|
27
|
+
"Command",
|
|
28
|
+
"DataTable",
|
|
29
|
+
"DatePicker",
|
|
30
|
+
"Dialog",
|
|
31
|
+
"Drawer",
|
|
32
|
+
"DropdownMenu",
|
|
33
|
+
"EmptyState",
|
|
34
|
+
"ErrorState",
|
|
35
|
+
"Field",
|
|
36
|
+
"Footer",
|
|
37
|
+
"Form",
|
|
38
|
+
"Header",
|
|
39
|
+
"Heading",
|
|
40
|
+
"Icon",
|
|
41
|
+
"Input",
|
|
42
|
+
"Kpi",
|
|
43
|
+
"Label",
|
|
44
|
+
"Link",
|
|
45
|
+
"Metric",
|
|
46
|
+
"Modal",
|
|
47
|
+
"Nav",
|
|
48
|
+
"Popover",
|
|
49
|
+
"Radio",
|
|
50
|
+
"Search",
|
|
51
|
+
"Select",
|
|
52
|
+
"Sheet",
|
|
53
|
+
"Sidebar",
|
|
54
|
+
"Skeleton",
|
|
55
|
+
"Spinner",
|
|
56
|
+
"Switch",
|
|
57
|
+
"Table",
|
|
58
|
+
"Tabs",
|
|
59
|
+
"Textarea",
|
|
60
|
+
"Toast",
|
|
61
|
+
"Toggle",
|
|
62
|
+
"Tooltip"
|
|
63
|
+
]);
|
|
64
|
+
var RAW_CONTROL_COMPONENT_BY_ELEMENT = {
|
|
65
|
+
button: "Button",
|
|
66
|
+
dialog: "Dialog",
|
|
67
|
+
input: "Input",
|
|
68
|
+
select: "Select",
|
|
69
|
+
table: "Table",
|
|
70
|
+
textarea: "Textarea"
|
|
71
|
+
};
|
|
72
|
+
function normalizePath(path) {
|
|
73
|
+
return path.replace(/\\/g, "/");
|
|
74
|
+
}
|
|
75
|
+
function isPrimitiveComponentName(name) {
|
|
76
|
+
return PRIMITIVE_COMPONENT_NAMES.has(name);
|
|
77
|
+
}
|
|
78
|
+
function isComponentName(name) {
|
|
79
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
80
|
+
}
|
|
81
|
+
function hasExportModifier(node) {
|
|
82
|
+
return Boolean(
|
|
83
|
+
ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
function containsJsx(node) {
|
|
87
|
+
let found = false;
|
|
88
|
+
const visit = (child) => {
|
|
89
|
+
if (found) return;
|
|
90
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child) || ts.isJsxOpeningElement(child)) {
|
|
91
|
+
found = true;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
ts.forEachChild(child, visit);
|
|
95
|
+
};
|
|
96
|
+
ts.forEachChild(node, visit);
|
|
97
|
+
return found;
|
|
98
|
+
}
|
|
99
|
+
function classExtendsReactComponent(node) {
|
|
100
|
+
return node.heritageClauses?.some(
|
|
101
|
+
(clause) => clause.types.some((type) => {
|
|
102
|
+
const text = type.expression.getText();
|
|
103
|
+
return /(?:^|\.)(?:Component|PureComponent)$/.test(text);
|
|
104
|
+
})
|
|
105
|
+
) ?? false;
|
|
106
|
+
}
|
|
107
|
+
function lineForNode(sourceFile, node) {
|
|
108
|
+
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
109
|
+
}
|
|
110
|
+
function jsxIdentifierName(name) {
|
|
111
|
+
return ts.isIdentifier(name) ? name.text : null;
|
|
112
|
+
}
|
|
113
|
+
function jsxAttributeStringValue(node, attributeName) {
|
|
114
|
+
for (const property of node.attributes.properties) {
|
|
115
|
+
if (!ts.isJsxAttribute(property) || !ts.isIdentifier(property.name) || property.name.text !== attributeName) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!property.initializer) return "";
|
|
119
|
+
if (ts.isStringLiteral(property.initializer)) return property.initializer.text;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function isHiddenInput(node) {
|
|
124
|
+
const tagName = jsxIdentifierName(node.tagName);
|
|
125
|
+
if (tagName !== "input") return false;
|
|
126
|
+
return jsxAttributeStringValue(node, "type")?.toLowerCase() === "hidden";
|
|
127
|
+
}
|
|
128
|
+
function isReusableComponentPath(file, name) {
|
|
129
|
+
const normalized = normalizePath(file);
|
|
130
|
+
const extension = extname(normalized);
|
|
131
|
+
const base = basename(normalized, extension).toLowerCase();
|
|
132
|
+
const componentName = name.toLowerCase();
|
|
133
|
+
return /(?:^|\/)(?:components|ui|shared|primitives|design-system|common)(?:\/|$)/i.test(normalized) || base === componentName && !/(?:^|\/)(?:app|pages|routes)(?:\/|$)/i.test(normalized) && !/(?:^|\/)(?:page|layout|route|loading|error|not-found)\.[cm]?[jt]sx?$/i.test(normalized);
|
|
134
|
+
}
|
|
135
|
+
function collectImports(sourceFile, file) {
|
|
136
|
+
const imports = /* @__PURE__ */ new Set();
|
|
137
|
+
const references = [];
|
|
138
|
+
for (const statement of sourceFile.statements) {
|
|
139
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
140
|
+
const clause = statement.importClause;
|
|
141
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
142
|
+
if (!ts.isStringLiteral(moduleSpecifier)) continue;
|
|
143
|
+
const defaultImport = clause?.name?.text;
|
|
144
|
+
const imported = [];
|
|
145
|
+
const localNames = [];
|
|
146
|
+
let namespaceImport;
|
|
147
|
+
if (!clause) continue;
|
|
148
|
+
if (clause.name) {
|
|
149
|
+
imports.add(clause.name.text);
|
|
150
|
+
localNames.push(clause.name.text);
|
|
151
|
+
}
|
|
152
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
153
|
+
for (const element of clause.namedBindings.elements) {
|
|
154
|
+
imports.add(element.name.text);
|
|
155
|
+
localNames.push(element.name.text);
|
|
156
|
+
imported.push((element.propertyName ?? element.name).text);
|
|
157
|
+
}
|
|
158
|
+
} else if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
|
|
159
|
+
namespaceImport = clause.namedBindings.name.text;
|
|
160
|
+
imports.add(namespaceImport);
|
|
161
|
+
localNames.push(namespaceImport);
|
|
162
|
+
}
|
|
163
|
+
references.push({
|
|
164
|
+
file,
|
|
165
|
+
source: moduleSpecifier.text,
|
|
166
|
+
line: lineForNode(sourceFile, statement),
|
|
167
|
+
...defaultImport ? { defaultImport } : {},
|
|
168
|
+
...namespaceImport ? { namespaceImport } : {},
|
|
169
|
+
imported,
|
|
170
|
+
localNames
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return { names: imports, references };
|
|
174
|
+
}
|
|
175
|
+
function collectDeclarations(sourceFile, file) {
|
|
176
|
+
const declarations = [];
|
|
177
|
+
const addDeclaration = (node, name, exported, kind) => {
|
|
178
|
+
if (!isComponentName(name) || !containsJsx(node)) return;
|
|
179
|
+
declarations.push({
|
|
180
|
+
id: `cmp:${file}:${name}`,
|
|
181
|
+
name,
|
|
182
|
+
file,
|
|
183
|
+
line: lineForNode(sourceFile, node),
|
|
184
|
+
exported,
|
|
185
|
+
reusable: exported && isReusableComponentPath(file, name),
|
|
186
|
+
kind
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const visit = (node) => {
|
|
190
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
191
|
+
addDeclaration(node, node.name.text, hasExportModifier(node), "function");
|
|
192
|
+
} else if (ts.isClassDeclaration(node) && node.name && classExtendsReactComponent(node)) {
|
|
193
|
+
addDeclaration(node, node.name.text, hasExportModifier(node), "class");
|
|
194
|
+
} else if (ts.isVariableStatement(node)) {
|
|
195
|
+
const exported = hasExportModifier(node);
|
|
196
|
+
for (const declaration of node.declarationList.declarations) {
|
|
197
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
198
|
+
addDeclaration(declaration, declaration.name.text, exported, "variable");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
ts.forEachChild(node, visit);
|
|
202
|
+
};
|
|
203
|
+
ts.forEachChild(sourceFile, visit);
|
|
204
|
+
return declarations;
|
|
205
|
+
}
|
|
206
|
+
function componentDeclarationName(node) {
|
|
207
|
+
if (ts.isFunctionDeclaration(node) && node.name && isComponentName(node.name.text)) {
|
|
208
|
+
return containsJsx(node) ? node.name.text : null;
|
|
209
|
+
}
|
|
210
|
+
if (ts.isClassDeclaration(node) && node.name && isComponentName(node.name.text) && classExtendsReactComponent(node)) {
|
|
211
|
+
return node.name.text;
|
|
212
|
+
}
|
|
213
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && isComponentName(node.name.text)) {
|
|
214
|
+
return node.initializer && containsJsx(node) ? node.name.text : null;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function collectRawControls(sourceFile, file) {
|
|
219
|
+
const rawControls = [];
|
|
220
|
+
const visit = (node, containingComponent) => {
|
|
221
|
+
const nextComponent = componentDeclarationName(node) ?? containingComponent;
|
|
222
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
223
|
+
const tagName = jsxIdentifierName(node.tagName);
|
|
224
|
+
const component = tagName && Object.hasOwn(RAW_CONTROL_COMPONENT_BY_ELEMENT, tagName) ? RAW_CONTROL_COMPONENT_BY_ELEMENT[tagName] : null;
|
|
225
|
+
if (component && !isHiddenInput(node)) {
|
|
226
|
+
rawControls.push({
|
|
227
|
+
element: tagName,
|
|
228
|
+
component,
|
|
229
|
+
file,
|
|
230
|
+
line: lineForNode(sourceFile, node),
|
|
231
|
+
...nextComponent ? { containingComponent: nextComponent } : {}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
ts.forEachChild(node, (child) => visit(child, nextComponent));
|
|
236
|
+
};
|
|
237
|
+
ts.forEachChild(sourceFile, (node) => visit(node));
|
|
238
|
+
return rawControls;
|
|
239
|
+
}
|
|
240
|
+
function parseComponentFile(projectRoot, absolutePath) {
|
|
241
|
+
const file = normalizePath(relative(projectRoot, absolutePath) || absolutePath);
|
|
242
|
+
if (isNonProductionComponentAuditFile(file)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
let code = "";
|
|
246
|
+
try {
|
|
247
|
+
code = readFileSync(absolutePath, "utf-8");
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const sourceFile = ts.createSourceFile(
|
|
252
|
+
file,
|
|
253
|
+
code,
|
|
254
|
+
ts.ScriptTarget.Latest,
|
|
255
|
+
true,
|
|
256
|
+
/\.[cm]?[jt]sx$/i.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
257
|
+
);
|
|
258
|
+
const imports = collectImports(sourceFile, file);
|
|
259
|
+
return {
|
|
260
|
+
file,
|
|
261
|
+
imports: imports.names,
|
|
262
|
+
importReferences: imports.references,
|
|
263
|
+
declarations: collectDeclarations(sourceFile, file),
|
|
264
|
+
rawControls: collectRawControls(sourceFile, file)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function canonicalSort(a, b) {
|
|
268
|
+
return a.file.localeCompare(b.file) || a.line - b.line || a.name.localeCompare(b.name);
|
|
269
|
+
}
|
|
270
|
+
function isNonProductionComponentAuditFile(file) {
|
|
271
|
+
const normalized = normalizePath(file);
|
|
272
|
+
return /(?:^|\/)(?:__tests__|__mocks__|tests?|specs?|fixtures?|mocks?|stories?)(?:\/|$)/i.test(
|
|
273
|
+
normalized
|
|
274
|
+
) || /\.(?:test|spec|stories|story|fixture|mock)\.[cm]?[jt]sx?$/i.test(normalized);
|
|
275
|
+
}
|
|
276
|
+
function auditComponentReuse(projectRoot, sourceFiles) {
|
|
277
|
+
const parsedFiles = sourceFiles.map((file) => parseComponentFile(projectRoot, file)).filter((file) => Boolean(file));
|
|
278
|
+
const declarations = parsedFiles.flatMap((file) => file.declarations);
|
|
279
|
+
const reusableByName = /* @__PURE__ */ new Map();
|
|
280
|
+
for (const declaration of declarations) {
|
|
281
|
+
if (!declaration.reusable || !isPrimitiveComponentName(declaration.name)) continue;
|
|
282
|
+
const existing = reusableByName.get(declaration.name) ?? [];
|
|
283
|
+
existing.push(declaration);
|
|
284
|
+
reusableByName.set(declaration.name, existing.sort(canonicalSort));
|
|
285
|
+
}
|
|
286
|
+
const findings = [];
|
|
287
|
+
const rawControlFindings = [];
|
|
288
|
+
for (const parsedFile of parsedFiles) {
|
|
289
|
+
for (const declaration of parsedFile.declarations) {
|
|
290
|
+
if (!isPrimitiveComponentName(declaration.name) || declaration.reusable) continue;
|
|
291
|
+
if (parsedFile.imports.has(declaration.name)) continue;
|
|
292
|
+
const canonical = reusableByName.get(declaration.name)?.filter((entry) => entry.file !== declaration.file).sort(canonicalSort)[0];
|
|
293
|
+
if (!canonical) continue;
|
|
294
|
+
findings.push({
|
|
295
|
+
id: `${COMPONENT_REUSE_RULE_ID}:${declaration.file}:${declaration.name}`,
|
|
296
|
+
name: declaration.name,
|
|
297
|
+
file: declaration.file,
|
|
298
|
+
line: declaration.line,
|
|
299
|
+
canonicalFile: canonical.file,
|
|
300
|
+
canonicalLine: canonical.line,
|
|
301
|
+
evidence: [
|
|
302
|
+
`${declaration.file}:${declaration.line} declares local ${declaration.name}`,
|
|
303
|
+
`${canonical.file}:${canonical.line} already exports reusable ${canonical.name}`
|
|
304
|
+
]
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const seenRawControls = /* @__PURE__ */ new Set();
|
|
308
|
+
for (const rawControl of parsedFile.rawControls) {
|
|
309
|
+
if (parsedFile.imports.has(rawControl.component)) continue;
|
|
310
|
+
if (rawControl.containingComponent === rawControl.component) continue;
|
|
311
|
+
const canonical = reusableByName.get(rawControl.component)?.filter((entry) => entry.file !== rawControl.file).sort(canonicalSort)[0];
|
|
312
|
+
if (!canonical) continue;
|
|
313
|
+
const key = `${rawControl.file}:${rawControl.component}`;
|
|
314
|
+
if (seenRawControls.has(key)) continue;
|
|
315
|
+
seenRawControls.add(key);
|
|
316
|
+
rawControlFindings.push({
|
|
317
|
+
id: `${RAW_CONTROL_REUSE_RULE_ID}:${rawControl.file}:${rawControl.element}`,
|
|
318
|
+
element: rawControl.element,
|
|
319
|
+
component: rawControl.component,
|
|
320
|
+
file: rawControl.file,
|
|
321
|
+
line: rawControl.line,
|
|
322
|
+
canonicalFile: canonical.file,
|
|
323
|
+
canonicalLine: canonical.line,
|
|
324
|
+
...rawControl.containingComponent ? { containingComponent: rawControl.containingComponent } : {},
|
|
325
|
+
evidence: [
|
|
326
|
+
`${rawControl.file}:${rawControl.line} renders raw <${rawControl.element}>`,
|
|
327
|
+
`${canonical.file}:${canonical.line} already exports reusable ${canonical.name}`
|
|
328
|
+
]
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
filesChecked: parsedFiles.length,
|
|
334
|
+
declarations,
|
|
335
|
+
imports: parsedFiles.flatMap((file) => file.importReferences),
|
|
336
|
+
reusableComponentCount: [...reusableByName.values()].reduce(
|
|
337
|
+
(count, entries) => count + entries.length,
|
|
338
|
+
0
|
|
339
|
+
),
|
|
340
|
+
localRedeclarationCount: findings.length,
|
|
341
|
+
rawControlCount: rawControlFindings.length,
|
|
342
|
+
findings,
|
|
343
|
+
rawControlFindings
|
|
344
|
+
};
|
|
345
|
+
}
|
|
8
346
|
|
|
9
347
|
// src/runtime.ts
|
|
10
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
348
|
+
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
11
349
|
import { createServer } from "http";
|
|
12
|
-
import { extname, join, normalize } from "path";
|
|
350
|
+
import { extname as extname2, join, normalize } from "path";
|
|
13
351
|
var CONTENT_TYPES = {
|
|
14
352
|
".css": "text/css; charset=utf-8",
|
|
15
353
|
".html": "text/html; charset=utf-8",
|
|
@@ -69,7 +407,7 @@ function emptyRuntimeAudit(failures = []) {
|
|
|
69
407
|
};
|
|
70
408
|
}
|
|
71
409
|
function getContentType(pathname) {
|
|
72
|
-
return CONTENT_TYPES[
|
|
410
|
+
return CONTENT_TYPES[extname2(pathname)] ?? "application/octet-stream";
|
|
73
411
|
}
|
|
74
412
|
function extractAssetPaths(indexHtml) {
|
|
75
413
|
const assetPaths = /* @__PURE__ */ new Set();
|
|
@@ -252,7 +590,7 @@ function auditNextBuildOutput(projectRoot) {
|
|
|
252
590
|
const relativePath = filePath.slice(appServerDir.length + 1).replace(/\\/g, "/");
|
|
253
591
|
return filePath.endsWith(".html") && !relativePath.startsWith("_");
|
|
254
592
|
});
|
|
255
|
-
const htmlDocuments = htmlFiles.map((filePath) =>
|
|
593
|
+
const htmlDocuments = htmlFiles.map((filePath) => readFileSync2(filePath, "utf-8"));
|
|
256
594
|
const staticAssetFiles = collectFiles(
|
|
257
595
|
join(nextDir, "static"),
|
|
258
596
|
(filePath) => /\.(?:js|css|json|svg|png|jpe?g|webp)$/i.test(filePath)
|
|
@@ -269,7 +607,7 @@ function auditNextBuildOutput(projectRoot) {
|
|
|
269
607
|
totalAssetBytes += byteLength;
|
|
270
608
|
if (assetFile.endsWith(".js")) {
|
|
271
609
|
jsAssetBytes += byteLength;
|
|
272
|
-
combinedJs +=
|
|
610
|
+
combinedJs += readFileSync2(assetFile, "utf-8");
|
|
273
611
|
}
|
|
274
612
|
if (assetFile.endsWith(".css")) {
|
|
275
613
|
cssAssetBytes += byteLength;
|
|
@@ -354,7 +692,7 @@ function isNextProject(projectRoot) {
|
|
|
354
692
|
return true;
|
|
355
693
|
}
|
|
356
694
|
try {
|
|
357
|
-
const pkg = JSON.parse(
|
|
695
|
+
const pkg = JSON.parse(readFileSync2(join(projectRoot, "package.json"), "utf-8"));
|
|
358
696
|
return Boolean(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
359
697
|
} catch {
|
|
360
698
|
return false;
|
|
@@ -371,13 +709,13 @@ async function startStaticServer(rootDir) {
|
|
|
371
709
|
if (existsSync(filePath)) {
|
|
372
710
|
res.statusCode = 200;
|
|
373
711
|
res.setHeader("Content-Type", getContentType(filePath));
|
|
374
|
-
res.end(
|
|
712
|
+
res.end(readFileSync2(filePath));
|
|
375
713
|
return;
|
|
376
714
|
}
|
|
377
715
|
if (existsSync(fallbackPath)) {
|
|
378
716
|
res.statusCode = 200;
|
|
379
717
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
380
|
-
res.end(
|
|
718
|
+
res.end(readFileSync2(fallbackPath));
|
|
381
719
|
return;
|
|
382
720
|
}
|
|
383
721
|
res.statusCode = 404;
|
|
@@ -422,7 +760,7 @@ async function auditBuiltDist(projectRoot, options = {}) {
|
|
|
422
760
|
}
|
|
423
761
|
const routeHints = Array.isArray(options.routeHints) && options.routeHints.length > 0 ? options.routeHints.map((route) => normalizeRouteHint(route)).filter(Boolean).slice(0, 8) : ["/"];
|
|
424
762
|
const frameworkDocumentOutput = isNextProject(projectRoot);
|
|
425
|
-
const indexHtml =
|
|
763
|
+
const indexHtml = readFileSync2(indexPath, "utf-8");
|
|
426
764
|
const assetPaths = extractAssetPaths(indexHtml);
|
|
427
765
|
const server = await startStaticServer(distDir);
|
|
428
766
|
try {
|
|
@@ -586,6 +924,862 @@ async function auditBuiltDist(projectRoot, options = {}) {
|
|
|
586
924
|
}
|
|
587
925
|
}
|
|
588
926
|
|
|
927
|
+
// src/style-bridge-drift.ts
|
|
928
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
929
|
+
import { join as join2, relative as relative2 } from "path";
|
|
930
|
+
import * as ts2 from "typescript";
|
|
931
|
+
var STYLE_BRIDGE_ARBITRARY_VALUE_RULE_ID = "style-bridge-arbitrary-value";
|
|
932
|
+
var CLASS_HELPER_NAMES = /* @__PURE__ */ new Set(["cn", "clsx", "classnames", "cva", "tv"]);
|
|
933
|
+
var INLINE_STYLE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
934
|
+
"accentColor",
|
|
935
|
+
"background",
|
|
936
|
+
"backgroundColor",
|
|
937
|
+
"border",
|
|
938
|
+
"borderColor",
|
|
939
|
+
"boxShadow",
|
|
940
|
+
"caretColor",
|
|
941
|
+
"color",
|
|
942
|
+
"fill",
|
|
943
|
+
"outline",
|
|
944
|
+
"outlineColor",
|
|
945
|
+
"stroke",
|
|
946
|
+
"textDecorationColor"
|
|
947
|
+
]);
|
|
948
|
+
var STYLESHEET_EXTENSIONS = /\.(?:css|scss|sass|less)$/i;
|
|
949
|
+
var SCRIPT_EXTENSIONS = /\.[cm]?[jt]sx?$/i;
|
|
950
|
+
var STYLESHEET_VISUAL_PROPERTIES = /* @__PURE__ */ new Set([
|
|
951
|
+
"accent-color",
|
|
952
|
+
"background",
|
|
953
|
+
"background-color",
|
|
954
|
+
"border",
|
|
955
|
+
"border-color",
|
|
956
|
+
"box-shadow",
|
|
957
|
+
"caret-color",
|
|
958
|
+
"color",
|
|
959
|
+
"fill",
|
|
960
|
+
"outline",
|
|
961
|
+
"outline-color",
|
|
962
|
+
"stroke",
|
|
963
|
+
"text-decoration-color"
|
|
964
|
+
]);
|
|
965
|
+
function normalizePath2(path) {
|
|
966
|
+
return path.replace(/\\/g, "/");
|
|
967
|
+
}
|
|
968
|
+
function stringArray(value) {
|
|
969
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
970
|
+
}
|
|
971
|
+
function isNonProductionStyleBridgeDriftFile(file) {
|
|
972
|
+
const normalized = normalizePath2(file);
|
|
973
|
+
return /(?:^|\/)(?:__tests__|__mocks__|tests?|specs?|fixtures?|mocks?|stories?)(?:\/|$)/i.test(
|
|
974
|
+
normalized
|
|
975
|
+
) || /\.(?:test|spec|stories|story|fixture|mock)\.(?:[cm]?[jt]sx?|css|scss|sass|less)$/i.test(
|
|
976
|
+
normalized
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
function collectProjectStyleFiles(projectRoot) {
|
|
980
|
+
const candidates = ["src", "app", "pages", "components", "styles"].map((dir) => join2(projectRoot, dir)).filter((dir) => existsSync2(dir));
|
|
981
|
+
const ignoredDirNames = /* @__PURE__ */ new Set([
|
|
982
|
+
"node_modules",
|
|
983
|
+
".git",
|
|
984
|
+
".decantr",
|
|
985
|
+
"dist",
|
|
986
|
+
"build",
|
|
987
|
+
"coverage"
|
|
988
|
+
]);
|
|
989
|
+
const files = /* @__PURE__ */ new Set();
|
|
990
|
+
const walk = (dir) => {
|
|
991
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
992
|
+
if (ignoredDirNames.has(entry.name)) continue;
|
|
993
|
+
const absolutePath = join2(dir, entry.name);
|
|
994
|
+
if (entry.isDirectory()) {
|
|
995
|
+
walk(absolutePath);
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
if (!entry.isFile() || !STYLESHEET_EXTENSIONS.test(absolutePath)) continue;
|
|
999
|
+
files.add(absolutePath);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
for (const candidate of candidates) {
|
|
1003
|
+
walk(candidate);
|
|
1004
|
+
}
|
|
1005
|
+
return [...files].sort();
|
|
1006
|
+
}
|
|
1007
|
+
function readAcceptedStyleBridge(projectRoot) {
|
|
1008
|
+
const path = join2(projectRoot, ".decantr", "style-bridge.json");
|
|
1009
|
+
if (!existsSync2(path)) return null;
|
|
1010
|
+
let bridge;
|
|
1011
|
+
try {
|
|
1012
|
+
bridge = JSON.parse(readFileSync3(path, "utf-8"));
|
|
1013
|
+
} catch {
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
if (bridge.status !== "accepted" || !Array.isArray(bridge.mappings)) return null;
|
|
1017
|
+
const mappings = bridge.mappings.filter(
|
|
1018
|
+
(mapping) => Boolean(mapping) && typeof mapping === "object"
|
|
1019
|
+
);
|
|
1020
|
+
if (mappings.length === 0) return null;
|
|
1021
|
+
const mappingIds = mappings.map(
|
|
1022
|
+
(mapping, index) => typeof mapping.id === "string" && mapping.id.length > 0 ? mapping.id : typeof mapping.label === "string" && mapping.label.length > 0 ? mapping.label : `mapping-${index + 1}`
|
|
1023
|
+
).slice(0, 12);
|
|
1024
|
+
const tokenHints = [
|
|
1025
|
+
...new Set(mappings.flatMap((mapping) => stringArray(mapping.tokenHints)))
|
|
1026
|
+
].slice(0, 16);
|
|
1027
|
+
const classHints = [
|
|
1028
|
+
...new Set(mappings.flatMap((mapping) => stringArray(mapping.classHints)))
|
|
1029
|
+
].slice(0, 16);
|
|
1030
|
+
return { mappingIds, tokenHints, classHints };
|
|
1031
|
+
}
|
|
1032
|
+
function lineForNode2(sourceFile, node) {
|
|
1033
|
+
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
1034
|
+
}
|
|
1035
|
+
function jsxAttributeName(name) {
|
|
1036
|
+
if (ts2.isIdentifier(name)) return name.text;
|
|
1037
|
+
if (ts2.isJsxNamespacedName(name)) return `${name.namespace.text}:${name.name.text}`;
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
function jsxAttributeText(attribute) {
|
|
1041
|
+
const initializer = attribute.initializer;
|
|
1042
|
+
if (!initializer) return "";
|
|
1043
|
+
if (ts2.isStringLiteral(initializer)) return initializer.text;
|
|
1044
|
+
if (!ts2.isJsxExpression(initializer) || !initializer.expression) return null;
|
|
1045
|
+
const expression = initializer.expression;
|
|
1046
|
+
if (ts2.isStringLiteral(expression) || ts2.isNoSubstitutionTemplateLiteral(expression)) {
|
|
1047
|
+
return expression.text;
|
|
1048
|
+
}
|
|
1049
|
+
if (ts2.isTemplateExpression(expression)) {
|
|
1050
|
+
return [
|
|
1051
|
+
expression.head.text,
|
|
1052
|
+
...expression.templateSpans.map((span) => span.literal.text)
|
|
1053
|
+
].join(" ");
|
|
1054
|
+
}
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
function classHelperName(expression) {
|
|
1058
|
+
if (ts2.isIdentifier(expression)) return expression.text;
|
|
1059
|
+
if (ts2.isPropertyAccessExpression(expression)) return expression.name.text;
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
function isClassHelperCall(node) {
|
|
1063
|
+
const helperName = classHelperName(node.expression);
|
|
1064
|
+
return Boolean(helperName && CLASS_HELPER_NAMES.has(helperName));
|
|
1065
|
+
}
|
|
1066
|
+
function templateExpressionText(expression) {
|
|
1067
|
+
return [
|
|
1068
|
+
expression.head.text,
|
|
1069
|
+
...expression.templateSpans.map((span) => span.literal.text)
|
|
1070
|
+
].join(" ");
|
|
1071
|
+
}
|
|
1072
|
+
function collectClassTextsFromNode(node) {
|
|
1073
|
+
const entries = [];
|
|
1074
|
+
const visit = (child) => {
|
|
1075
|
+
if (ts2.isStringLiteral(child) || ts2.isNoSubstitutionTemplateLiteral(child)) {
|
|
1076
|
+
entries.push({ className: child.text, node: child });
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
if (ts2.isTemplateExpression(child)) {
|
|
1080
|
+
entries.push({ className: templateExpressionText(child), node: child });
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
ts2.forEachChild(child, visit);
|
|
1084
|
+
};
|
|
1085
|
+
ts2.forEachChild(node, visit);
|
|
1086
|
+
return entries;
|
|
1087
|
+
}
|
|
1088
|
+
function stripTailwindVariants(className) {
|
|
1089
|
+
let bracketDepth = 0;
|
|
1090
|
+
let segmentStart = 0;
|
|
1091
|
+
for (let index = 0; index < className.length; index += 1) {
|
|
1092
|
+
const char = className[index];
|
|
1093
|
+
if (char === "[") bracketDepth += 1;
|
|
1094
|
+
if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
1095
|
+
if (char === ":" && bracketDepth === 0) segmentStart = index + 1;
|
|
1096
|
+
}
|
|
1097
|
+
return className.slice(segmentStart);
|
|
1098
|
+
}
|
|
1099
|
+
function arbitraryStyleClasses(className) {
|
|
1100
|
+
const classes = className.split(/\s+/).map((entry) => entry.trim()).filter(Boolean);
|
|
1101
|
+
return classes.filter((entry) => {
|
|
1102
|
+
const classWithoutVariant = stripTailwindVariants(entry);
|
|
1103
|
+
return /^(?:-)?(?:bg|text|border|ring|shadow|from|via|to|fill|stroke|outline|decoration|accent|caret|placeholder|divide)-\[[^\]]+\]$/i.test(
|
|
1104
|
+
classWithoutVariant
|
|
1105
|
+
) || /^\[(?:color|background|background-color|border|border-color|box-shadow|outline|fill|stroke|--[A-Za-z0-9_-]+):[^\]]+\]$/i.test(
|
|
1106
|
+
classWithoutVariant
|
|
1107
|
+
);
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
function propertyName(name) {
|
|
1111
|
+
if (ts2.isIdentifier(name) || ts2.isStringLiteral(name) || ts2.isNumericLiteral(name)) {
|
|
1112
|
+
return name.text;
|
|
1113
|
+
}
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
function styleLiteralValue(expression) {
|
|
1117
|
+
if (ts2.isStringLiteral(expression) || ts2.isNoSubstitutionTemplateLiteral(expression)) {
|
|
1118
|
+
return expression.text;
|
|
1119
|
+
}
|
|
1120
|
+
if (ts2.isTemplateExpression(expression)) {
|
|
1121
|
+
return expression.templateSpans.length === 0 ? expression.head.text : null;
|
|
1122
|
+
}
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
function isAcceptedTokenValue(value, tokenHints) {
|
|
1126
|
+
return tokenHints.some((hint) => {
|
|
1127
|
+
const normalizedHint = hint.trim();
|
|
1128
|
+
if (!normalizedHint) return false;
|
|
1129
|
+
return value.includes(normalizedHint) || value.includes(`var(${normalizedHint})`);
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
function arbitraryInlineStyleValue(property, value, tokenHints) {
|
|
1133
|
+
const normalizedProperty = property.trim();
|
|
1134
|
+
const normalizedValue = value.trim();
|
|
1135
|
+
if (!normalizedProperty || !normalizedValue) return null;
|
|
1136
|
+
const isVisualProperty = INLINE_STYLE_PROPERTIES.has(normalizedProperty) || STYLESHEET_VISUAL_PROPERTIES.has(normalizedProperty) || normalizedProperty.startsWith("--");
|
|
1137
|
+
if (!isVisualProperty) return null;
|
|
1138
|
+
if (isAcceptedTokenValue(normalizedValue, tokenHints)) return null;
|
|
1139
|
+
if (/(?:^|[\s,(])#[0-9a-f]{3,8}\b|(?:rgba?|hsla?|oklch|oklab|lch|lab|color-mix)\(/i.test(
|
|
1140
|
+
normalizedValue
|
|
1141
|
+
) || /var\(--[A-Za-z0-9_-]+\)/i.test(normalizedValue)) {
|
|
1142
|
+
return `${normalizedProperty}: ${normalizedValue}`;
|
|
1143
|
+
}
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
function styleBridgeEvidenceText(file, line, source, value) {
|
|
1147
|
+
if (source === "inline-style") {
|
|
1148
|
+
return `${file}:${line} uses inline style value "${value}"`;
|
|
1149
|
+
}
|
|
1150
|
+
if (source === "stylesheet") {
|
|
1151
|
+
return `${file}:${line} uses stylesheet value "${value}"`;
|
|
1152
|
+
}
|
|
1153
|
+
return `${file}:${line} uses arbitrary Tailwind value "${value}"`;
|
|
1154
|
+
}
|
|
1155
|
+
function collectStyleBridgeDriftFindings(input) {
|
|
1156
|
+
const file = normalizePath2(relative2(input.projectRoot, input.absolutePath) || input.absolutePath);
|
|
1157
|
+
if (isNonProductionStyleBridgeDriftFile(file)) return [];
|
|
1158
|
+
if (!SCRIPT_EXTENSIONS.test(file)) return [];
|
|
1159
|
+
let code = "";
|
|
1160
|
+
try {
|
|
1161
|
+
code = readFileSync3(input.absolutePath, "utf-8");
|
|
1162
|
+
} catch {
|
|
1163
|
+
return [];
|
|
1164
|
+
}
|
|
1165
|
+
const sourceFile = ts2.createSourceFile(
|
|
1166
|
+
file,
|
|
1167
|
+
code,
|
|
1168
|
+
ts2.ScriptTarget.Latest,
|
|
1169
|
+
true,
|
|
1170
|
+
/\.[cm]?[jt]sx$/i.test(file) ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
|
|
1171
|
+
);
|
|
1172
|
+
const findings = [];
|
|
1173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1174
|
+
const addFinding = (node, className, value, source, property) => {
|
|
1175
|
+
const line = lineForNode2(sourceFile, node);
|
|
1176
|
+
const key = `${file}:${line}:${value}`;
|
|
1177
|
+
if (seen.has(key)) return;
|
|
1178
|
+
seen.add(key);
|
|
1179
|
+
findings.push({
|
|
1180
|
+
id: `${STYLE_BRIDGE_ARBITRARY_VALUE_RULE_ID}:${file}:${line}:${value}`,
|
|
1181
|
+
file,
|
|
1182
|
+
line,
|
|
1183
|
+
className,
|
|
1184
|
+
value,
|
|
1185
|
+
source,
|
|
1186
|
+
...property ? { property } : {},
|
|
1187
|
+
bridgeMappingIds: input.bridge.mappingIds,
|
|
1188
|
+
tokenHints: input.bridge.tokenHints,
|
|
1189
|
+
classHints: input.bridge.classHints,
|
|
1190
|
+
evidence: [
|
|
1191
|
+
styleBridgeEvidenceText(file, line, source, value),
|
|
1192
|
+
`.decantr/style-bridge.json is accepted with mappings: ${input.bridge.mappingIds.join(", ")}`,
|
|
1193
|
+
input.bridge.tokenHints.length > 0 ? `Accepted token hints: ${input.bridge.tokenHints.join(", ")}` : "Accepted style bridge has no token hints for this mapping set",
|
|
1194
|
+
input.bridge.classHints.length > 0 ? `Accepted class hints: ${input.bridge.classHints.join(", ")}` : "Accepted style bridge has no class hints for this mapping set"
|
|
1195
|
+
]
|
|
1196
|
+
});
|
|
1197
|
+
};
|
|
1198
|
+
const visit = (node) => {
|
|
1199
|
+
if (ts2.isJsxAttribute(node) && jsxAttributeName(node.name) === "className") {
|
|
1200
|
+
const className = jsxAttributeText(node);
|
|
1201
|
+
if (className) {
|
|
1202
|
+
for (const value of arbitraryStyleClasses(className)) {
|
|
1203
|
+
addFinding(node, className, value, "className");
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (ts2.isJsxAttribute(node) && jsxAttributeName(node.name) === "style") {
|
|
1208
|
+
const initializer = node.initializer;
|
|
1209
|
+
const expression = initializer && ts2.isJsxExpression(initializer) ? initializer.expression : void 0;
|
|
1210
|
+
if (expression && ts2.isObjectLiteralExpression(expression)) {
|
|
1211
|
+
for (const property of expression.properties) {
|
|
1212
|
+
if (!ts2.isPropertyAssignment(property)) continue;
|
|
1213
|
+
const name = propertyName(property.name);
|
|
1214
|
+
const literal = styleLiteralValue(property.initializer);
|
|
1215
|
+
if (!name || literal === null) continue;
|
|
1216
|
+
const value = arbitraryInlineStyleValue(name, literal, input.bridge.tokenHints);
|
|
1217
|
+
if (value) {
|
|
1218
|
+
addFinding(property, `style.${name}`, value, "inline-style", name);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (ts2.isCallExpression(node) && isClassHelperCall(node)) {
|
|
1224
|
+
for (const entry of collectClassTextsFromNode(node)) {
|
|
1225
|
+
for (const value of arbitraryStyleClasses(entry.className)) {
|
|
1226
|
+
addFinding(entry.node, entry.className, value, "className");
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
ts2.forEachChild(node, visit);
|
|
1231
|
+
};
|
|
1232
|
+
ts2.forEachChild(sourceFile, visit);
|
|
1233
|
+
return findings;
|
|
1234
|
+
}
|
|
1235
|
+
function collectStylesheetBridgeDriftFindings(input) {
|
|
1236
|
+
const file = normalizePath2(relative2(input.projectRoot, input.absolutePath) || input.absolutePath);
|
|
1237
|
+
if (isNonProductionStyleBridgeDriftFile(file) || !STYLESHEET_EXTENSIONS.test(file)) return [];
|
|
1238
|
+
let code = "";
|
|
1239
|
+
try {
|
|
1240
|
+
code = readFileSync3(input.absolutePath, "utf-8");
|
|
1241
|
+
} catch {
|
|
1242
|
+
return [];
|
|
1243
|
+
}
|
|
1244
|
+
const findings = [];
|
|
1245
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1246
|
+
const lines = code.split(/\r?\n/);
|
|
1247
|
+
const declarationPattern = /(^|[;{\s])(?<property>accent-color|background|background-color|border|border-color|box-shadow|caret-color|color|fill|outline|outline-color|stroke|text-decoration-color)\s*:\s*(?<value>[^;{}]+)\s*;?/gi;
|
|
1248
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
1249
|
+
if (/^\s*(?:\/\*|\*|\/\/)/.test(line)) continue;
|
|
1250
|
+
for (const match of line.matchAll(declarationPattern)) {
|
|
1251
|
+
const property = match.groups?.property;
|
|
1252
|
+
const rawValue = match.groups?.value;
|
|
1253
|
+
if (!property || !rawValue) continue;
|
|
1254
|
+
const value = arbitraryInlineStyleValue(property, rawValue, input.bridge.tokenHints);
|
|
1255
|
+
if (!value) continue;
|
|
1256
|
+
const lineNumber = lineIndex + 1;
|
|
1257
|
+
const key = `${file}:${lineNumber}:${value}`;
|
|
1258
|
+
if (seen.has(key)) continue;
|
|
1259
|
+
seen.add(key);
|
|
1260
|
+
findings.push({
|
|
1261
|
+
id: `${STYLE_BRIDGE_ARBITRARY_VALUE_RULE_ID}:${file}:${lineNumber}:${value}`,
|
|
1262
|
+
file,
|
|
1263
|
+
line: lineNumber,
|
|
1264
|
+
className: property,
|
|
1265
|
+
value,
|
|
1266
|
+
source: "stylesheet",
|
|
1267
|
+
property,
|
|
1268
|
+
bridgeMappingIds: input.bridge.mappingIds,
|
|
1269
|
+
tokenHints: input.bridge.tokenHints,
|
|
1270
|
+
classHints: input.bridge.classHints,
|
|
1271
|
+
evidence: [
|
|
1272
|
+
`${file}:${lineNumber} uses stylesheet value "${value}"`,
|
|
1273
|
+
`.decantr/style-bridge.json is accepted with mappings: ${input.bridge.mappingIds.join(", ")}`,
|
|
1274
|
+
input.bridge.tokenHints.length > 0 ? `Accepted token hints: ${input.bridge.tokenHints.join(", ")}` : "Accepted style bridge has no token hints for this mapping set",
|
|
1275
|
+
input.bridge.classHints.length > 0 ? `Accepted class hints: ${input.bridge.classHints.join(", ")}` : "Accepted style bridge has no class hints for this mapping set"
|
|
1276
|
+
]
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return findings;
|
|
1281
|
+
}
|
|
1282
|
+
function auditStyleBridgeDrift(projectRoot, sourceFiles) {
|
|
1283
|
+
const bridge = readAcceptedStyleBridge(projectRoot);
|
|
1284
|
+
if (!bridge) {
|
|
1285
|
+
return {
|
|
1286
|
+
filesChecked: 0,
|
|
1287
|
+
bridgeMappingCount: 0,
|
|
1288
|
+
arbitraryValueCount: 0,
|
|
1289
|
+
findings: []
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
const filesToCheck = [
|
|
1293
|
+
.../* @__PURE__ */ new Set([...sourceFiles, ...collectProjectStyleFiles(projectRoot)])
|
|
1294
|
+
].sort();
|
|
1295
|
+
const findings = filesToCheck.flatMap(
|
|
1296
|
+
(absolutePath) => SCRIPT_EXTENSIONS.test(absolutePath) ? collectStyleBridgeDriftFindings({ projectRoot, absolutePath, bridge }) : collectStylesheetBridgeDriftFindings({ projectRoot, absolutePath, bridge })
|
|
1297
|
+
);
|
|
1298
|
+
return {
|
|
1299
|
+
filesChecked: filesToCheck.filter(
|
|
1300
|
+
(file) => !isNonProductionStyleBridgeDriftFile(
|
|
1301
|
+
normalizePath2(relative2(projectRoot, file) || file)
|
|
1302
|
+
)
|
|
1303
|
+
).length,
|
|
1304
|
+
bridgeMappingCount: bridge.mappingIds.length,
|
|
1305
|
+
arbitraryValueCount: findings.length,
|
|
1306
|
+
findings
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/diagnostics.ts
|
|
1311
|
+
var KNOWN_VERIFICATION_DIAGNOSTICS = [
|
|
1312
|
+
{
|
|
1313
|
+
rule: "browser-base-url-missing",
|
|
1314
|
+
code: "VISUAL002",
|
|
1315
|
+
repairId: "provide-browser-base-url",
|
|
1316
|
+
family: "VISUAL"
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
rule: "browser-playwright-missing",
|
|
1320
|
+
code: "VISUAL001",
|
|
1321
|
+
repairId: "install-browser-verifier",
|
|
1322
|
+
family: "VISUAL"
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
rule: "browser-route-verification-failed",
|
|
1326
|
+
code: "VISUAL003",
|
|
1327
|
+
repairId: "fix-route-render",
|
|
1328
|
+
family: "VISUAL"
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
rule: "visual-baseline-screenshot-drift",
|
|
1332
|
+
code: "VISUAL010",
|
|
1333
|
+
repairId: "review-visual-baseline-drift",
|
|
1334
|
+
family: "VISUAL"
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
rule: "brownfield-route-drift",
|
|
1338
|
+
code: "ROUTE001",
|
|
1339
|
+
repairId: "reconcile-brownfield-routes",
|
|
1340
|
+
family: "ROUTE"
|
|
1341
|
+
},
|
|
1342
|
+
{ rule: "check-failed", code: "CHECK001", repairId: "repair-contract-check", family: "CHECK" },
|
|
1343
|
+
{
|
|
1344
|
+
rule: "component-reuse-raw-control",
|
|
1345
|
+
code: "COMP010",
|
|
1346
|
+
repairId: "replace-raw-control-with-local-component",
|
|
1347
|
+
family: "COMP"
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
rule: "component-reuse-primitive-reimplemented",
|
|
1351
|
+
code: "COMP001",
|
|
1352
|
+
repairId: "import-existing-component",
|
|
1353
|
+
family: "COMP"
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
rule: "declared-route-resolves",
|
|
1357
|
+
code: "STRUCT002",
|
|
1358
|
+
repairId: "repair-route-binding",
|
|
1359
|
+
family: "STRUCT"
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
rule: "design-token-coverage",
|
|
1363
|
+
code: "TOKEN002",
|
|
1364
|
+
repairId: "repair-design-token-coverage",
|
|
1365
|
+
family: "TOKEN"
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
rule: "style-bridge-arbitrary-value",
|
|
1369
|
+
code: "TOKEN010",
|
|
1370
|
+
repairId: "replace-arbitrary-style-with-bridge-token",
|
|
1371
|
+
family: "TOKEN"
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
rule: "essence-present",
|
|
1375
|
+
code: "CONTRACT001",
|
|
1376
|
+
repairId: "restore-essence-contract",
|
|
1377
|
+
family: "CONTRACT"
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
rule: "essence-v4",
|
|
1381
|
+
code: "CONTRACT002",
|
|
1382
|
+
repairId: "migrate-essence-v4",
|
|
1383
|
+
family: "CONTRACT"
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
rule: "focus-visible-enabled",
|
|
1387
|
+
code: "A11Y001",
|
|
1388
|
+
repairId: "enable-focus-visible",
|
|
1389
|
+
family: "A11Y"
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
rule: "pack-manifest-present",
|
|
1393
|
+
code: "CTX002",
|
|
1394
|
+
repairId: "hydrate-execution-packs",
|
|
1395
|
+
family: "CTX"
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
rule: "pack-manifest-missing",
|
|
1399
|
+
code: "CTX002",
|
|
1400
|
+
repairId: "hydrate-execution-packs",
|
|
1401
|
+
family: "CTX"
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
rule: "page-pack-count-mismatch",
|
|
1405
|
+
code: "CTX001",
|
|
1406
|
+
repairId: "regenerate-context-packs",
|
|
1407
|
+
family: "CTX"
|
|
1408
|
+
},
|
|
1409
|
+
{ rule: "page-route-required", code: "STRUCT001", repairId: "add-page-route", family: "STRUCT" },
|
|
1410
|
+
{
|
|
1411
|
+
rule: "project-audit-invalid",
|
|
1412
|
+
code: "AUDIT001",
|
|
1413
|
+
repairId: "repair-project-audit",
|
|
1414
|
+
family: "AUDIT"
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
rule: "review-pack-present",
|
|
1418
|
+
code: "CTX003",
|
|
1419
|
+
repairId: "hydrate-review-pack",
|
|
1420
|
+
family: "CTX"
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
rule: "review-pack-file-missing",
|
|
1424
|
+
code: "CTX003",
|
|
1425
|
+
repairId: "hydrate-review-pack",
|
|
1426
|
+
family: "CTX"
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
rule: "section-shell-concrete",
|
|
1430
|
+
code: "STRUCT003",
|
|
1431
|
+
repairId: "assign-section-shell",
|
|
1432
|
+
family: "STRUCT"
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
rule: "tokens-file-present",
|
|
1436
|
+
code: "TOKEN001",
|
|
1437
|
+
repairId: "restore-design-token-file",
|
|
1438
|
+
family: "TOKEN"
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
rule: "typed-graph-current",
|
|
1442
|
+
code: "GRAPH001",
|
|
1443
|
+
repairId: "regenerate-typed-graph",
|
|
1444
|
+
family: "GRAPH"
|
|
1445
|
+
}
|
|
1446
|
+
];
|
|
1447
|
+
var CODE_BY_RULE = Object.fromEntries(
|
|
1448
|
+
KNOWN_VERIFICATION_DIAGNOSTICS.map((entry) => [entry.rule, entry.code])
|
|
1449
|
+
);
|
|
1450
|
+
var REPAIR_BY_RULE = Object.fromEntries(
|
|
1451
|
+
KNOWN_VERIFICATION_DIAGNOSTICS.map((entry) => [entry.rule, entry.repairId])
|
|
1452
|
+
);
|
|
1453
|
+
var DEFAULT_REPAIR_BY_SOURCE = {
|
|
1454
|
+
assertion: "repair-contract-assertion",
|
|
1455
|
+
audit: "repair-audit-finding",
|
|
1456
|
+
browser: "repair-browser-evidence",
|
|
1457
|
+
brownfield: "repair-brownfield-drift",
|
|
1458
|
+
check: "repair-contract-check",
|
|
1459
|
+
"design-token": "repair-design-token-drift",
|
|
1460
|
+
"style-bridge": "repair-style-bridge-drift",
|
|
1461
|
+
graph: "regenerate-typed-graph",
|
|
1462
|
+
interaction: "repair-interaction-contract",
|
|
1463
|
+
pack: "regenerate-context-packs",
|
|
1464
|
+
runtime: "repair-runtime-output"
|
|
1465
|
+
};
|
|
1466
|
+
var DEFAULT_CODE_PREFIX_BY_SOURCE = {
|
|
1467
|
+
assertion: "CONTRACT",
|
|
1468
|
+
audit: "AUDIT",
|
|
1469
|
+
browser: "VISUAL",
|
|
1470
|
+
brownfield: "ROUTE",
|
|
1471
|
+
check: "CHECK",
|
|
1472
|
+
"design-token": "TOKEN",
|
|
1473
|
+
"style-bridge": "TOKEN",
|
|
1474
|
+
graph: "GRAPH",
|
|
1475
|
+
interaction: "INT",
|
|
1476
|
+
pack: "CTX",
|
|
1477
|
+
runtime: "RUNTIME"
|
|
1478
|
+
};
|
|
1479
|
+
function stableNumber(value) {
|
|
1480
|
+
let hash = 0;
|
|
1481
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1482
|
+
hash = hash * 31 + value.charCodeAt(i) >>> 0;
|
|
1483
|
+
}
|
|
1484
|
+
return String(hash % 900 + 100).padStart(3, "0");
|
|
1485
|
+
}
|
|
1486
|
+
function normalizedKey(value) {
|
|
1487
|
+
return (value ?? "").trim().toLowerCase();
|
|
1488
|
+
}
|
|
1489
|
+
function codePrefix(input) {
|
|
1490
|
+
const source = normalizedKey(input.source);
|
|
1491
|
+
if (source && DEFAULT_CODE_PREFIX_BY_SOURCE[source]) return DEFAULT_CODE_PREFIX_BY_SOURCE[source];
|
|
1492
|
+
const category = normalizedKey(input.category);
|
|
1493
|
+
if (category.includes("accessibility")) return "A11Y";
|
|
1494
|
+
if (category.includes("route")) return "ROUTE";
|
|
1495
|
+
if (category.includes("token")) return "TOKEN";
|
|
1496
|
+
if (category.includes("runtime")) return "RUNTIME";
|
|
1497
|
+
if (category.includes("pack") || category.includes("context")) return "CTX";
|
|
1498
|
+
if (category.includes("visual") || category.includes("browser")) return "VISUAL";
|
|
1499
|
+
if (category.includes("interaction")) return "INT";
|
|
1500
|
+
if (category.includes("contract")) return "CONTRACT";
|
|
1501
|
+
return "DCTR";
|
|
1502
|
+
}
|
|
1503
|
+
function lookupById(map, input) {
|
|
1504
|
+
const id = normalizedKey(input.id);
|
|
1505
|
+
if (!id) return void 0;
|
|
1506
|
+
if (map[id]) return map[id];
|
|
1507
|
+
const source = normalizedKey(input.source);
|
|
1508
|
+
const idWithoutSource = source && id.startsWith(`${source}-`) ? id.slice(source.length + 1) : id;
|
|
1509
|
+
if (map[idWithoutSource]) return map[idWithoutSource];
|
|
1510
|
+
const suffix = Object.keys(map).find((key) => id.endsWith(`-${key}`));
|
|
1511
|
+
return suffix ? map[suffix] : void 0;
|
|
1512
|
+
}
|
|
1513
|
+
function diagnosticCode(input) {
|
|
1514
|
+
const rule = normalizedKey(input.rule);
|
|
1515
|
+
if (rule && CODE_BY_RULE[rule]) return CODE_BY_RULE[rule];
|
|
1516
|
+
const idCode = lookupById(CODE_BY_RULE, input);
|
|
1517
|
+
if (idCode) return idCode;
|
|
1518
|
+
const seed = [input.source, input.rule, input.id, input.category, input.message].filter((entry) => typeof entry === "string" && entry.length > 0).join("|");
|
|
1519
|
+
return `${codePrefix(input)}${stableNumber(seed)}`;
|
|
1520
|
+
}
|
|
1521
|
+
function repairId(input) {
|
|
1522
|
+
const rule = normalizedKey(input.rule);
|
|
1523
|
+
if (rule && REPAIR_BY_RULE[rule]) return REPAIR_BY_RULE[rule];
|
|
1524
|
+
const idRepair = lookupById(REPAIR_BY_RULE, input);
|
|
1525
|
+
if (idRepair) return idRepair;
|
|
1526
|
+
const source = normalizedKey(input.source);
|
|
1527
|
+
return DEFAULT_REPAIR_BY_SOURCE[source] ?? "repair-project-health-finding";
|
|
1528
|
+
}
|
|
1529
|
+
function compactPayload(input) {
|
|
1530
|
+
return Object.fromEntries(
|
|
1531
|
+
Object.entries(input).filter(([, value]) => {
|
|
1532
|
+
if (value === void 0 || value === null) return false;
|
|
1533
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
1534
|
+
if (typeof value === "string") return value.length > 0;
|
|
1535
|
+
return true;
|
|
1536
|
+
})
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
function deriveVerificationDiagnostic(input) {
|
|
1540
|
+
const payload = compactPayload({
|
|
1541
|
+
source: input.source,
|
|
1542
|
+
rule: input.rule,
|
|
1543
|
+
target: input.target,
|
|
1544
|
+
file: input.file,
|
|
1545
|
+
suggested_fix: input.suggestedFix,
|
|
1546
|
+
evidence: input.evidence?.slice(0, 5)
|
|
1547
|
+
});
|
|
1548
|
+
const repair = { id: repairId(input) };
|
|
1549
|
+
if (Object.keys(payload).length > 0) repair.payload = payload;
|
|
1550
|
+
return {
|
|
1551
|
+
code: diagnosticCode(input),
|
|
1552
|
+
repair
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/graph-anchors.ts
|
|
1557
|
+
function payloadRecord(payload) {
|
|
1558
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
1559
|
+
}
|
|
1560
|
+
function payloadString(payload, key) {
|
|
1561
|
+
const value = payloadRecord(payload)[key];
|
|
1562
|
+
return typeof value === "string" ? value : void 0;
|
|
1563
|
+
}
|
|
1564
|
+
function slug(value) {
|
|
1565
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_.:/-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1566
|
+
}
|
|
1567
|
+
function normalizedHaystack(finding) {
|
|
1568
|
+
return [
|
|
1569
|
+
finding.id,
|
|
1570
|
+
finding.category,
|
|
1571
|
+
finding.message,
|
|
1572
|
+
finding.target,
|
|
1573
|
+
finding.file,
|
|
1574
|
+
finding.rule,
|
|
1575
|
+
...finding.evidence ?? []
|
|
1576
|
+
].filter((entry) => typeof entry === "string" && entry.length > 0).join("\n").toLowerCase();
|
|
1577
|
+
}
|
|
1578
|
+
function graphAnchor(snapshot, node, confidence, reason, route) {
|
|
1579
|
+
const anchor = {
|
|
1580
|
+
snapshot_id: snapshot.id,
|
|
1581
|
+
source_hash: snapshot.source_hash,
|
|
1582
|
+
node_id: node.id,
|
|
1583
|
+
node_type: node.type,
|
|
1584
|
+
confidence,
|
|
1585
|
+
reason
|
|
1586
|
+
};
|
|
1587
|
+
if (route) anchor.route = route;
|
|
1588
|
+
return anchor;
|
|
1589
|
+
}
|
|
1590
|
+
function routeForNode(snapshot, nodeId, visited = /* @__PURE__ */ new Set()) {
|
|
1591
|
+
if (visited.has(nodeId)) return void 0;
|
|
1592
|
+
visited.add(nodeId);
|
|
1593
|
+
if (nodeId.startsWith("rt:")) {
|
|
1594
|
+
const route2 = snapshot.nodes.find((node) => node.id === nodeId);
|
|
1595
|
+
return payloadString(route2?.payload, "path") ?? nodeId.replace(/^rt:/, "");
|
|
1596
|
+
}
|
|
1597
|
+
const sourceNode = snapshot.nodes.find(
|
|
1598
|
+
(node) => node.id === nodeId && node.type === "SourceArtifact"
|
|
1599
|
+
);
|
|
1600
|
+
if (sourceNode) {
|
|
1601
|
+
const sourceEdges = snapshot.edges.filter(
|
|
1602
|
+
(edge) => edge.relation === "NODE_DERIVED_FROM_SOURCE" && edge.dst === nodeId
|
|
1603
|
+
);
|
|
1604
|
+
const sortedSourceEdges = [
|
|
1605
|
+
...sourceEdges.filter((edge) => edge.src.startsWith("rt:")),
|
|
1606
|
+
...sourceEdges.filter((edge) => !edge.src.startsWith("rt:"))
|
|
1607
|
+
];
|
|
1608
|
+
for (const edge of sortedSourceEdges) {
|
|
1609
|
+
const route2 = routeForNode(snapshot, edge.src, visited);
|
|
1610
|
+
if (route2) return route2;
|
|
1611
|
+
}
|
|
1612
|
+
return void 0;
|
|
1613
|
+
}
|
|
1614
|
+
const pageId = snapshot.nodes.find((node) => node.id === nodeId && node.type === "Page")?.id ?? snapshot.edges.find((edge) => edge.relation === "PAGE_COMPOSES_PATTERN" && edge.dst === nodeId)?.src ?? snapshot.edges.find((edge) => edge.relation === "PAGE_USES_SHELL" && edge.dst === nodeId)?.src;
|
|
1615
|
+
if (!pageId) return void 0;
|
|
1616
|
+
const routeEdge = snapshot.edges.find(
|
|
1617
|
+
(edge) => edge.relation === "PAGE_ROUTED_AT_ROUTE" && edge.src === pageId
|
|
1618
|
+
);
|
|
1619
|
+
if (!routeEdge) return void 0;
|
|
1620
|
+
const route = snapshot.nodes.find((node) => node.id === routeEdge.dst);
|
|
1621
|
+
return payloadString(route?.payload, "path") ?? routeEdge.dst.replace(/^rt:/, "");
|
|
1622
|
+
}
|
|
1623
|
+
function findProjectNode(snapshot) {
|
|
1624
|
+
return snapshot.nodes.find((node) => node.id === snapshot.project_id) ?? snapshot.nodes.find((node) => node.type === "Project");
|
|
1625
|
+
}
|
|
1626
|
+
function findLocalRuleNode(snapshot, finding) {
|
|
1627
|
+
const candidates = [finding.rule, finding.target, finding.id].filter((entry) => typeof entry === "string" && entry.length > 0).map(slug);
|
|
1628
|
+
if (candidates.length === 0) return null;
|
|
1629
|
+
return snapshot.nodes.find((node) => {
|
|
1630
|
+
if (node.type !== "LocalRule") return false;
|
|
1631
|
+
const payloadId = payloadString(node.payload, "id");
|
|
1632
|
+
const ids = [node.id.replace(/^rule:/, ""), payloadId ? slug(payloadId) : null].filter(
|
|
1633
|
+
(entry) => Boolean(entry)
|
|
1634
|
+
);
|
|
1635
|
+
return ids.some((id) => candidates.some((candidate) => id === candidate));
|
|
1636
|
+
}) ?? null;
|
|
1637
|
+
}
|
|
1638
|
+
function findStyleBridgeNode(snapshot, finding) {
|
|
1639
|
+
const haystack = normalizedHaystack(finding);
|
|
1640
|
+
const candidates = [finding.rule, finding.target, finding.id, ...finding.evidence ?? []].filter((entry) => typeof entry === "string" && entry.length > 0).map(slug);
|
|
1641
|
+
const styleBridgeNodes = snapshot.nodes.filter((node) => node.type === "StyleBridge");
|
|
1642
|
+
if (styleBridgeNodes.length === 0) return null;
|
|
1643
|
+
return styleBridgeNodes.find((node) => {
|
|
1644
|
+
const payloadId = payloadString(node.payload, "id");
|
|
1645
|
+
const payloadLabel = payloadString(node.payload, "label");
|
|
1646
|
+
const ids = [
|
|
1647
|
+
node.id.replace(/^bridge:/, ""),
|
|
1648
|
+
payloadId ? slug(payloadId) : null,
|
|
1649
|
+
payloadLabel ? slug(payloadLabel) : null
|
|
1650
|
+
].filter((entry) => Boolean(entry));
|
|
1651
|
+
return ids.some(
|
|
1652
|
+
(id) => candidates.some((candidate) => id === candidate || candidate.includes(id)) || haystack.includes(id.toLowerCase())
|
|
1653
|
+
);
|
|
1654
|
+
}) ?? (haystack.includes("style-bridge") || haystack.includes("style bridge") ? styleBridgeNodes[0] ?? null : null);
|
|
1655
|
+
}
|
|
1656
|
+
function findRouteNode(snapshot, finding) {
|
|
1657
|
+
const haystack = normalizedHaystack(finding);
|
|
1658
|
+
const exactTarget = finding.target?.startsWith("/") ? finding.target : null;
|
|
1659
|
+
const routes = snapshot.nodes.filter((node) => node.type === "Route").map((node) => ({
|
|
1660
|
+
node,
|
|
1661
|
+
path: payloadString(node.payload, "path") ?? node.id.replace(/^rt:/, "")
|
|
1662
|
+
})).sort((a, b) => b.path.length - a.path.length);
|
|
1663
|
+
return routes.find((route) => route.path === exactTarget)?.node ?? routes.find((route) => route.path !== "/" && haystack.includes(route.path.toLowerCase()))?.node ?? null;
|
|
1664
|
+
}
|
|
1665
|
+
function findPageNode(snapshot, finding) {
|
|
1666
|
+
const candidates = [finding.target, finding.id].filter((entry) => typeof entry === "string" && entry.length > 0).map(slug);
|
|
1667
|
+
if (candidates.length === 0) return null;
|
|
1668
|
+
return snapshot.nodes.find((node) => {
|
|
1669
|
+
if (node.type !== "Page") return false;
|
|
1670
|
+
const payloadId = payloadString(node.payload, "id");
|
|
1671
|
+
const payloadSection = payloadString(node.payload, "section");
|
|
1672
|
+
const ids = [
|
|
1673
|
+
node.id.replace(/^pg:/, ""),
|
|
1674
|
+
payloadId ? slug(payloadId) : null,
|
|
1675
|
+
payloadId && payloadSection ? slug(`${payloadSection}:${payloadId}`) : null
|
|
1676
|
+
].filter((entry) => Boolean(entry));
|
|
1677
|
+
return ids.some((id) => candidates.some((candidate) => id === candidate));
|
|
1678
|
+
}) ?? null;
|
|
1679
|
+
}
|
|
1680
|
+
function findPatternNode(snapshot, finding) {
|
|
1681
|
+
const haystack = normalizedHaystack(finding);
|
|
1682
|
+
return snapshot.nodes.find((node) => {
|
|
1683
|
+
if (node.type !== "Pattern") return false;
|
|
1684
|
+
const payloadId = payloadString(node.payload, "id");
|
|
1685
|
+
return [node.id.replace(/^pat:/, ""), payloadId].filter((entry) => Boolean(entry)).some((id) => haystack.includes(id.toLowerCase()));
|
|
1686
|
+
}) ?? null;
|
|
1687
|
+
}
|
|
1688
|
+
function findSourceArtifactNode(snapshot, finding) {
|
|
1689
|
+
const haystack = normalizedHaystack(finding);
|
|
1690
|
+
return snapshot.nodes.find((node) => {
|
|
1691
|
+
if (node.type !== "SourceArtifact") return false;
|
|
1692
|
+
const path = payloadString(node.payload, "path");
|
|
1693
|
+
return path ? haystack.includes(path.toLowerCase()) : false;
|
|
1694
|
+
}) ?? null;
|
|
1695
|
+
}
|
|
1696
|
+
function normalizePath3(value) {
|
|
1697
|
+
return value ? value.replace(/\\/g, "/").toLowerCase() : null;
|
|
1698
|
+
}
|
|
1699
|
+
function findExactSourceArtifactNode(snapshot, finding) {
|
|
1700
|
+
const findingFile = normalizePath3(finding.file);
|
|
1701
|
+
if (!findingFile) return null;
|
|
1702
|
+
return snapshot.nodes.find((node) => {
|
|
1703
|
+
if (node.type !== "SourceArtifact") return false;
|
|
1704
|
+
return normalizePath3(payloadString(node.payload, "path")) === findingFile;
|
|
1705
|
+
}) ?? null;
|
|
1706
|
+
}
|
|
1707
|
+
function resolveGraphAnchorForFinding(snapshot, finding) {
|
|
1708
|
+
if (!snapshot) return void 0;
|
|
1709
|
+
const localRule = findLocalRuleNode(snapshot, finding);
|
|
1710
|
+
if (localRule) {
|
|
1711
|
+
return graphAnchor(snapshot, localRule, "exact", "finding rule matched a LocalRule node");
|
|
1712
|
+
}
|
|
1713
|
+
const exactSourceArtifact = findExactSourceArtifactNode(snapshot, finding);
|
|
1714
|
+
if (exactSourceArtifact) {
|
|
1715
|
+
return graphAnchor(
|
|
1716
|
+
snapshot,
|
|
1717
|
+
exactSourceArtifact,
|
|
1718
|
+
"exact",
|
|
1719
|
+
"finding file matched a SourceArtifact node",
|
|
1720
|
+
routeForNode(snapshot, exactSourceArtifact.id)
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
const styleBridge = findStyleBridgeNode(snapshot, finding);
|
|
1724
|
+
if (styleBridge) {
|
|
1725
|
+
return graphAnchor(snapshot, styleBridge, "exact", "finding matched a StyleBridge node");
|
|
1726
|
+
}
|
|
1727
|
+
const route = findRouteNode(snapshot, finding);
|
|
1728
|
+
if (route) {
|
|
1729
|
+
return graphAnchor(
|
|
1730
|
+
snapshot,
|
|
1731
|
+
route,
|
|
1732
|
+
finding.target === payloadString(route.payload, "path") ? "exact" : "inferred",
|
|
1733
|
+
"finding text matched a Route node",
|
|
1734
|
+
payloadString(route.payload, "path") ?? route.id.replace(/^rt:/, "")
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
const page = findPageNode(snapshot, finding);
|
|
1738
|
+
if (page) {
|
|
1739
|
+
return graphAnchor(
|
|
1740
|
+
snapshot,
|
|
1741
|
+
page,
|
|
1742
|
+
"exact",
|
|
1743
|
+
"finding target matched a Page node",
|
|
1744
|
+
routeForNode(snapshot, page.id)
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
const pattern = findPatternNode(snapshot, finding);
|
|
1748
|
+
if (pattern) {
|
|
1749
|
+
return graphAnchor(
|
|
1750
|
+
snapshot,
|
|
1751
|
+
pattern,
|
|
1752
|
+
"inferred",
|
|
1753
|
+
"finding text matched a Pattern node",
|
|
1754
|
+
routeForNode(snapshot, pattern.id)
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
const sourceArtifact = findSourceArtifactNode(snapshot, finding);
|
|
1758
|
+
if (sourceArtifact) {
|
|
1759
|
+
return graphAnchor(
|
|
1760
|
+
snapshot,
|
|
1761
|
+
sourceArtifact,
|
|
1762
|
+
"inferred",
|
|
1763
|
+
"finding evidence matched a SourceArtifact node",
|
|
1764
|
+
routeForNode(snapshot, sourceArtifact.id)
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
const project = findProjectNode(snapshot);
|
|
1768
|
+
return project ? graphAnchor(
|
|
1769
|
+
snapshot,
|
|
1770
|
+
project,
|
|
1771
|
+
"fallback",
|
|
1772
|
+
"finding is project-level or no specific graph node matched"
|
|
1773
|
+
) : void 0;
|
|
1774
|
+
}
|
|
1775
|
+
function anchorFindingsToGraph(snapshot, findings) {
|
|
1776
|
+
if (!snapshot) return findings;
|
|
1777
|
+
return findings.map((finding) => {
|
|
1778
|
+
const graph = resolveGraphAnchorForFinding(snapshot, finding);
|
|
1779
|
+
return graph ? { ...finding, graph } : finding;
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
|
|
589
1783
|
// src/interactions.ts
|
|
590
1784
|
var INTERACTION_SIGNALS = {
|
|
591
1785
|
"animate-on-mount": {
|
|
@@ -755,8 +1949,9 @@ function listKnownInteractions() {
|
|
|
755
1949
|
}
|
|
756
1950
|
|
|
757
1951
|
// src/scan.ts
|
|
758
|
-
import { existsSync as
|
|
759
|
-
import { extname as
|
|
1952
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
1953
|
+
import { extname as extname3, isAbsolute, join as join3, relative as relative3 } from "path";
|
|
1954
|
+
import { decodeHTML, decodeHTMLAttribute } from "entities";
|
|
760
1955
|
var SCAN_REPORT_SCHEMA_URL = "https://decantr.ai/schemas/scan-report.v1.json";
|
|
761
1956
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"]);
|
|
762
1957
|
var STYLE_EXTENSIONS = /* @__PURE__ */ new Set([".css", ".scss", ".sass", ".less"]);
|
|
@@ -788,6 +1983,27 @@ var RULE_FILES = [
|
|
|
788
1983
|
"copilot-instructions.md",
|
|
789
1984
|
".windsurfrules"
|
|
790
1985
|
];
|
|
1986
|
+
var GITHUB_OWNER_RE = /^[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?$/i;
|
|
1987
|
+
var GITHUB_REPO_RE = /^[a-z0-9._-]{1,100}$/i;
|
|
1988
|
+
function parseHttpUrl(value) {
|
|
1989
|
+
try {
|
|
1990
|
+
const url = new URL(value.trim());
|
|
1991
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url : null;
|
|
1992
|
+
} catch {
|
|
1993
|
+
return null;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
function githubPagesOwnerFromHostname(hostname) {
|
|
1997
|
+
const [owner, domain, tld, ...extra] = hostname.toLowerCase().split(".");
|
|
1998
|
+
if (extra.length > 0 || domain !== "github" || tld !== "io" || !owner) return null;
|
|
1999
|
+
return GITHUB_OWNER_RE.test(owner) ? owner : null;
|
|
2000
|
+
}
|
|
2001
|
+
function normalizeGitHubPathSegment(segment, kind) {
|
|
2002
|
+
if (!segment) return null;
|
|
2003
|
+
const normalized = kind === "repo" ? segment.replace(/\.git$/i, "") : segment;
|
|
2004
|
+
const pattern = kind === "owner" ? GITHUB_OWNER_RE : GITHUB_REPO_RE;
|
|
2005
|
+
return pattern.test(normalized) ? normalized : null;
|
|
2006
|
+
}
|
|
791
2007
|
var WEB_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
792
2008
|
"angular",
|
|
793
2009
|
"astro",
|
|
@@ -806,14 +2022,14 @@ function readTextFile(path, maxBytes = MAX_FILE_READ_BYTES) {
|
|
|
806
2022
|
try {
|
|
807
2023
|
const stat = statSync2(path);
|
|
808
2024
|
if (!stat.isFile() || stat.size > maxBytes) return null;
|
|
809
|
-
return
|
|
2025
|
+
return readFileSync4(path, "utf-8");
|
|
810
2026
|
} catch {
|
|
811
2027
|
return null;
|
|
812
2028
|
}
|
|
813
2029
|
}
|
|
814
2030
|
function readPackageJson(projectRoot) {
|
|
815
|
-
const path =
|
|
816
|
-
if (!
|
|
2031
|
+
const path = join3(projectRoot, "package.json");
|
|
2032
|
+
if (!existsSync3(path)) return { value: null, present: false, valid: false };
|
|
817
2033
|
const content = readTextFile(path);
|
|
818
2034
|
if (!content) return { value: null, present: true, valid: false };
|
|
819
2035
|
try {
|
|
@@ -831,17 +2047,17 @@ function dependencyVersion(dependencies, names) {
|
|
|
831
2047
|
return null;
|
|
832
2048
|
}
|
|
833
2049
|
function hasAnyFile(projectRoot, paths) {
|
|
834
|
-
return paths.some((path) =>
|
|
2050
|
+
return paths.some((path) => existsSync3(join3(projectRoot, path)));
|
|
835
2051
|
}
|
|
836
2052
|
function detectPackageManager(projectRoot, pkg) {
|
|
837
2053
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
838
2054
|
if (declared === "npm" || declared === "pnpm" || declared === "yarn" || declared === "bun") {
|
|
839
2055
|
return declared;
|
|
840
2056
|
}
|
|
841
|
-
if (
|
|
842
|
-
if (
|
|
843
|
-
if (
|
|
844
|
-
if (
|
|
2057
|
+
if (existsSync3(join3(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
2058
|
+
if (existsSync3(join3(projectRoot, "yarn.lock"))) return "yarn";
|
|
2059
|
+
if (existsSync3(join3(projectRoot, "bun.lockb"))) return "bun";
|
|
2060
|
+
if (existsSync3(join3(projectRoot, "package-lock.json"))) return "npm";
|
|
845
2061
|
return "unknown";
|
|
846
2062
|
}
|
|
847
2063
|
function detectPrimaryLanguage(projectRoot, packageJsonPresent) {
|
|
@@ -897,7 +2113,7 @@ function detectProject(projectRoot) {
|
|
|
897
2113
|
"tailwind.config.mjs",
|
|
898
2114
|
"tailwind.config.cjs"
|
|
899
2115
|
]),
|
|
900
|
-
hasDecantr:
|
|
2116
|
+
hasDecantr: existsSync3(join3(projectRoot, "decantr.essence.json")) || existsSync3(join3(projectRoot, ".decantr")) || Boolean(dependencies["@decantr/cli"] || dependencies["@decantr/css"]),
|
|
901
2117
|
packageName: typeof pkg?.name === "string" ? pkg.name : null,
|
|
902
2118
|
packageJsonValid: packageRead.valid,
|
|
903
2119
|
packageJsonPresent: packageRead.present,
|
|
@@ -913,13 +2129,13 @@ function walkFiles(projectRoot, options = {}) {
|
|
|
913
2129
|
if (files.length >= MAX_WALK_FILES) return;
|
|
914
2130
|
let entries;
|
|
915
2131
|
try {
|
|
916
|
-
entries =
|
|
2132
|
+
entries = readdirSync3(dir);
|
|
917
2133
|
} catch {
|
|
918
2134
|
return;
|
|
919
2135
|
}
|
|
920
2136
|
for (const entry of entries) {
|
|
921
2137
|
if (files.length >= MAX_WALK_FILES) return;
|
|
922
|
-
const fullPath =
|
|
2138
|
+
const fullPath = join3(dir, entry);
|
|
923
2139
|
let stat;
|
|
924
2140
|
try {
|
|
925
2141
|
stat = statSync2(fullPath);
|
|
@@ -932,8 +2148,8 @@ function walkFiles(projectRoot, options = {}) {
|
|
|
932
2148
|
continue;
|
|
933
2149
|
}
|
|
934
2150
|
if (!stat.isFile()) continue;
|
|
935
|
-
const rel =
|
|
936
|
-
const ext =
|
|
2151
|
+
const rel = relative3(projectRoot, fullPath).replace(/\\/g, "/");
|
|
2152
|
+
const ext = extname3(entry).toLowerCase();
|
|
937
2153
|
if (!options.extensions || options.extensions.has(ext)) {
|
|
938
2154
|
files.push(rel);
|
|
939
2155
|
}
|
|
@@ -955,7 +2171,7 @@ function segmentToRoute(segment) {
|
|
|
955
2171
|
function walkNextAppRoutes(dir, projectRoot, segments) {
|
|
956
2172
|
let entries;
|
|
957
2173
|
try {
|
|
958
|
-
entries =
|
|
2174
|
+
entries = readdirSync3(dir);
|
|
959
2175
|
} catch {
|
|
960
2176
|
return [];
|
|
961
2177
|
}
|
|
@@ -965,13 +2181,13 @@ function walkNextAppRoutes(dir, projectRoot, segments) {
|
|
|
965
2181
|
if (pageFile) {
|
|
966
2182
|
routes.push({
|
|
967
2183
|
path: `/${segments.filter(Boolean).join("/")}` || "/",
|
|
968
|
-
file:
|
|
2184
|
+
file: relative3(projectRoot, join3(dir, pageFile)).replace(/\\/g, "/"),
|
|
969
2185
|
hasLayout
|
|
970
2186
|
});
|
|
971
2187
|
}
|
|
972
2188
|
for (const entry of entries) {
|
|
973
2189
|
if (shouldSkipDir(entry)) continue;
|
|
974
|
-
const fullPath =
|
|
2190
|
+
const fullPath = join3(dir, entry);
|
|
975
2191
|
try {
|
|
976
2192
|
if (!statSync2(fullPath).isDirectory()) continue;
|
|
977
2193
|
} catch {
|
|
@@ -983,17 +2199,17 @@ function walkNextAppRoutes(dir, projectRoot, segments) {
|
|
|
983
2199
|
return routes;
|
|
984
2200
|
}
|
|
985
2201
|
function fileRouteFromPath(file, baseDir) {
|
|
986
|
-
let withoutExt = file.slice(0, -
|
|
2202
|
+
let withoutExt = file.slice(0, -extname3(file).length);
|
|
987
2203
|
if (withoutExt.endsWith("/index")) withoutExt = withoutExt.slice(0, -"/index".length);
|
|
988
2204
|
withoutExt = withoutExt.replace(new RegExp(`^${baseDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "");
|
|
989
2205
|
const parts = withoutExt.split("/").filter(Boolean).map((part) => segmentToRoute(part)).filter(Boolean);
|
|
990
2206
|
return `/${parts.join("/")}` || "/";
|
|
991
2207
|
}
|
|
992
2208
|
function scanFileRoutes(projectRoot, baseDir, extensions = PAGE_EXTENSIONS) {
|
|
993
|
-
const fullBase =
|
|
994
|
-
if (!
|
|
2209
|
+
const fullBase = join3(projectRoot, baseDir);
|
|
2210
|
+
if (!existsSync3(fullBase)) return [];
|
|
995
2211
|
return walkFiles(fullBase, { extensions }).map((file) => {
|
|
996
|
-
const rel =
|
|
2212
|
+
const rel = relative3(projectRoot, join3(fullBase, file)).replace(/\\/g, "/");
|
|
997
2213
|
return { path: fileRouteFromPath(rel, baseDir), file: rel, hasLayout: false };
|
|
998
2214
|
});
|
|
999
2215
|
}
|
|
@@ -1002,7 +2218,7 @@ function scanReactRouter(projectRoot) {
|
|
|
1002
2218
|
const routes = /* @__PURE__ */ new Map();
|
|
1003
2219
|
let hashRouting = false;
|
|
1004
2220
|
for (const file of files) {
|
|
1005
|
-
const content = readTextFile(
|
|
2221
|
+
const content = readTextFile(join3(projectRoot, file));
|
|
1006
2222
|
if (!content) continue;
|
|
1007
2223
|
if (content.includes("HashRouter") || content.includes("createHashRouter")) hashRouting = true;
|
|
1008
2224
|
const jsxRouteRegex = /<Route\b[^>]*\bpath\s*=\s*["']([^"']+)["']/g;
|
|
@@ -1021,7 +2237,7 @@ function scanReactRouter(projectRoot) {
|
|
|
1021
2237
|
}
|
|
1022
2238
|
function scanRoutes(projectRoot, detection) {
|
|
1023
2239
|
const appRoutes = ["src/app", "app"].flatMap(
|
|
1024
|
-
(dir) =>
|
|
2240
|
+
(dir) => existsSync3(join3(projectRoot, dir)) ? walkNextAppRoutes(join3(projectRoot, dir), projectRoot, []) : []
|
|
1025
2241
|
);
|
|
1026
2242
|
const pagesRoutes = ["src/pages", "pages"].flatMap((dir) => scanFileRoutes(projectRoot, dir));
|
|
1027
2243
|
if (detection.framework === "nextjs") {
|
|
@@ -1044,10 +2260,10 @@ function scanRoutes(projectRoot, detection) {
|
|
|
1044
2260
|
const reactRouter = scanReactRouter(projectRoot);
|
|
1045
2261
|
if (reactRouter.routes.length > 0) return { strategy: "react-router", routes: reactRouter.routes };
|
|
1046
2262
|
if (pagesRoutes.length > 0) return { strategy: "pages-router", routes: pagesRoutes };
|
|
1047
|
-
if (
|
|
2263
|
+
if (existsSync3(join3(projectRoot, "index.html"))) {
|
|
1048
2264
|
return { strategy: "static-html", routes: [{ path: "/", file: "index.html", hasLayout: false }] };
|
|
1049
2265
|
}
|
|
1050
|
-
if (
|
|
2266
|
+
if (existsSync3(join3(projectRoot, "docs", "index.html"))) {
|
|
1051
2267
|
return { strategy: "static-html", routes: [{ path: "/", file: "docs/index.html", hasLayout: false }] };
|
|
1052
2268
|
}
|
|
1053
2269
|
return { strategy: "none", routes: [] };
|
|
@@ -1089,12 +2305,12 @@ function scanStyling(projectRoot, detection) {
|
|
|
1089
2305
|
let configFile = null;
|
|
1090
2306
|
let approach = "unknown";
|
|
1091
2307
|
const tailwindConfig = ["tailwind.config.js", "tailwind.config.ts", "tailwind.config.mjs", "tailwind.config.cjs"].find(
|
|
1092
|
-
(file) =>
|
|
2308
|
+
(file) => existsSync3(join3(projectRoot, file))
|
|
1093
2309
|
);
|
|
1094
2310
|
if (tailwindConfig || deps.tailwindcss) {
|
|
1095
2311
|
approach = "tailwind";
|
|
1096
2312
|
configFile = tailwindConfig ?? "package.json";
|
|
1097
|
-
} else if (deps["@decantr/css"] ||
|
|
2313
|
+
} else if (deps["@decantr/css"] || existsSync3(join3(projectRoot, "src/styles/tokens.css")) && existsSync3(join3(projectRoot, "src/styles/treatments.css"))) {
|
|
1098
2314
|
approach = "decantr-css";
|
|
1099
2315
|
configFile = deps["@decantr/css"] ? "package.json" : "src/styles/tokens.css";
|
|
1100
2316
|
} else if (deps.bootstrap) {
|
|
@@ -1112,7 +2328,7 @@ function scanStyling(projectRoot, detection) {
|
|
|
1112
2328
|
approach = "css";
|
|
1113
2329
|
}
|
|
1114
2330
|
for (const file of cssFiles.slice(0, 80)) {
|
|
1115
|
-
const content = readTextFile(
|
|
2331
|
+
const content = readTextFile(join3(projectRoot, file), 256 * 1024);
|
|
1116
2332
|
if (!content) continue;
|
|
1117
2333
|
const evidence = extractCssEvidence(content);
|
|
1118
2334
|
cssVariableCount += evidence.variables;
|
|
@@ -1130,7 +2346,7 @@ function scanStyling(projectRoot, detection) {
|
|
|
1130
2346
|
};
|
|
1131
2347
|
}
|
|
1132
2348
|
function findAssistantRules(projectRoot) {
|
|
1133
|
-
return RULE_FILES.filter((file) =>
|
|
2349
|
+
return RULE_FILES.filter((file) => existsSync3(join3(projectRoot, file)));
|
|
1134
2350
|
}
|
|
1135
2351
|
function scanStaticHosting(projectRoot, detection) {
|
|
1136
2352
|
const packageRead = readPackageJson(projectRoot);
|
|
@@ -1139,25 +2355,22 @@ function scanStaticHosting(projectRoot, detection) {
|
|
|
1139
2355
|
const homepageUrl = typeof pkg?.homepage === "string" ? pkg.homepage : null;
|
|
1140
2356
|
let basePath = null;
|
|
1141
2357
|
let hashRouting = false;
|
|
1142
|
-
|
|
2358
|
+
const homepage = homepageUrl ? parseHttpUrl(homepageUrl) : null;
|
|
2359
|
+
if (homepage && githubPagesOwnerFromHostname(homepage.hostname)) {
|
|
1143
2360
|
evidence.push("package.json homepage points at GitHub Pages");
|
|
1144
|
-
|
|
1145
|
-
const url = new URL(homepageUrl);
|
|
1146
|
-
basePath = url.pathname && url.pathname !== "/" ? url.pathname : null;
|
|
1147
|
-
} catch {
|
|
1148
|
-
}
|
|
2361
|
+
basePath = homepage.pathname && homepage.pathname !== "/" ? homepage.pathname : null;
|
|
1149
2362
|
}
|
|
1150
2363
|
if (detection.dependencies["gh-pages"] || pkg?.scripts?.deploy?.includes("gh-pages")) {
|
|
1151
2364
|
evidence.push("package scripts or dependencies reference gh-pages");
|
|
1152
2365
|
}
|
|
1153
|
-
if (
|
|
1154
|
-
if (
|
|
2366
|
+
if (existsSync3(join3(projectRoot, "docs", "index.html"))) evidence.push("docs/index.html can serve GitHub Pages");
|
|
2367
|
+
if (existsSync3(join3(projectRoot, "404.html")) || existsSync3(join3(projectRoot, "docs", "404.html"))) {
|
|
1155
2368
|
evidence.push("404.html fallback is present");
|
|
1156
2369
|
}
|
|
1157
|
-
const workflowDir =
|
|
1158
|
-
if (
|
|
2370
|
+
const workflowDir = join3(projectRoot, ".github", "workflows");
|
|
2371
|
+
if (existsSync3(workflowDir)) {
|
|
1159
2372
|
for (const file of walkFiles(workflowDir, { extensions: /* @__PURE__ */ new Set([".yml", ".yaml"]) }).slice(0, 20)) {
|
|
1160
|
-
const content = readTextFile(
|
|
2373
|
+
const content = readTextFile(join3(workflowDir, file));
|
|
1161
2374
|
if (content && /pages|gh-pages|upload-pages-artifact|deploy-pages/i.test(content)) {
|
|
1162
2375
|
evidence.push(`GitHub Pages workflow hint: .github/workflows/${file}`);
|
|
1163
2376
|
break;
|
|
@@ -1166,7 +2379,7 @@ function scanStaticHosting(projectRoot, detection) {
|
|
|
1166
2379
|
}
|
|
1167
2380
|
const sourceFiles = walkFiles(projectRoot, { extensions: SOURCE_EXTENSIONS });
|
|
1168
2381
|
for (const file of sourceFiles.slice(0, 200)) {
|
|
1169
|
-
const content = readTextFile(
|
|
2382
|
+
const content = readTextFile(join3(projectRoot, file), 128 * 1024);
|
|
1170
2383
|
if (!content) continue;
|
|
1171
2384
|
if (content.includes("HashRouter") || content.includes("createHashRouter")) {
|
|
1172
2385
|
hashRouting = true;
|
|
@@ -1175,7 +2388,7 @@ function scanStaticHosting(projectRoot, detection) {
|
|
|
1175
2388
|
}
|
|
1176
2389
|
}
|
|
1177
2390
|
for (const config of ["vite.config.ts", "vite.config.js", "vite.config.mjs"]) {
|
|
1178
|
-
const content = readTextFile(
|
|
2391
|
+
const content = readTextFile(join3(projectRoot, config), 128 * 1024);
|
|
1179
2392
|
const baseMatch = content?.match(/\bbase\s*:\s*['"]([^'"]+)['"]/);
|
|
1180
2393
|
if (baseMatch?.[1]) {
|
|
1181
2394
|
basePath = baseMatch[1];
|
|
@@ -1482,12 +2695,15 @@ function createUnavailableScanReport(input) {
|
|
|
1482
2695
|
}
|
|
1483
2696
|
};
|
|
1484
2697
|
}
|
|
1485
|
-
function
|
|
1486
|
-
return value
|
|
2698
|
+
function decodeHtmlText(value) {
|
|
2699
|
+
return decodeHTML(value).replace(/\s+/g, " ").trim();
|
|
2700
|
+
}
|
|
2701
|
+
function decodeHtmlAttributeValue(value) {
|
|
2702
|
+
return decodeHTMLAttribute(value).trim();
|
|
1487
2703
|
}
|
|
1488
2704
|
function extractHtmlAttribute(tag, attr) {
|
|
1489
2705
|
const match = tag.match(new RegExp(`${attr}\\s*=\\s*["']([^"']+)["']`, "i"));
|
|
1490
|
-
return match?.[1] ?
|
|
2706
|
+
return match?.[1] ? decodeHtmlAttributeValue(match[1]) : null;
|
|
1491
2707
|
}
|
|
1492
2708
|
async function probePublishedSite(url, options = {}) {
|
|
1493
2709
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -1519,7 +2735,7 @@ async function probePublishedSite(url, options = {}) {
|
|
|
1519
2735
|
checked: true,
|
|
1520
2736
|
reachable: response.ok,
|
|
1521
2737
|
status: response.status,
|
|
1522
|
-
title: titleMatch?.[1] ?
|
|
2738
|
+
title: titleMatch?.[1] ? decodeHtmlText(titleMatch[1]) : null,
|
|
1523
2739
|
description: descriptionTag ? extractHtmlAttribute(descriptionTag, "content") : null,
|
|
1524
2740
|
canonicalUrl: canonicalTag ? extractHtmlAttribute(canonicalTag, "href") : null,
|
|
1525
2741
|
assetHints: {
|
|
@@ -1550,22 +2766,21 @@ async function probePublishedSite(url, options = {}) {
|
|
|
1550
2766
|
}
|
|
1551
2767
|
}
|
|
1552
2768
|
function resolveGitHubScanInput(input) {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
url = new URL(input.trim());
|
|
1556
|
-
} catch {
|
|
2769
|
+
const url = parseHttpUrl(input);
|
|
2770
|
+
if (!url) {
|
|
1557
2771
|
throw new Error("Enter a valid GitHub repository URL or GitHub Pages URL.");
|
|
1558
2772
|
}
|
|
1559
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
1560
|
-
throw new Error("Scan URLs must use http or https.");
|
|
1561
|
-
}
|
|
1562
2773
|
const host = url.hostname.toLowerCase();
|
|
1563
2774
|
if (host === "github.com" || host === "www.github.com") {
|
|
1564
|
-
const [
|
|
2775
|
+
const [ownerSegment, repoSegment] = url.pathname.split("/").filter(Boolean);
|
|
2776
|
+
const owner = normalizeGitHubPathSegment(ownerSegment, "owner");
|
|
2777
|
+
const repo = normalizeGitHubPathSegment(repoSegment, "repo");
|
|
1565
2778
|
if (!owner || !repoSegment) {
|
|
1566
2779
|
throw new Error("GitHub repository URLs must include owner and repository.");
|
|
1567
2780
|
}
|
|
1568
|
-
|
|
2781
|
+
if (!repo) {
|
|
2782
|
+
throw new Error("GitHub repository URLs must include a valid owner and repository.");
|
|
2783
|
+
}
|
|
1569
2784
|
return {
|
|
1570
2785
|
inputKind: "github-repo",
|
|
1571
2786
|
normalizedInput: `https://github.com/${owner}/${repo}`,
|
|
@@ -1578,18 +2793,21 @@ function resolveGitHubScanInput(input) {
|
|
|
1578
2793
|
warnings: url.pathname.split("/").filter(Boolean).length > 2 ? ["Extra GitHub URL path segments were ignored."] : []
|
|
1579
2794
|
};
|
|
1580
2795
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
2796
|
+
const pagesOwner = githubPagesOwnerFromHostname(host);
|
|
2797
|
+
if (pagesOwner) {
|
|
1583
2798
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
1584
|
-
const repo = segments[0]
|
|
1585
|
-
|
|
2799
|
+
const repo = segments[0] ? normalizeGitHubPathSegment(segments[0], "repo") : `${pagesOwner}.github.io`;
|
|
2800
|
+
if (!repo) {
|
|
2801
|
+
throw new Error("GitHub Pages URLs must include a valid owner and repository path.");
|
|
2802
|
+
}
|
|
2803
|
+
const publishedSiteUrl = segments[0] ? `https://${pagesOwner}.github.io/${repo}/` : `https://${pagesOwner}.github.io/`;
|
|
1586
2804
|
return {
|
|
1587
2805
|
inputKind: "github-pages",
|
|
1588
2806
|
normalizedInput: publishedSiteUrl,
|
|
1589
2807
|
repository: {
|
|
1590
|
-
owner,
|
|
2808
|
+
owner: pagesOwner,
|
|
1591
2809
|
repo,
|
|
1592
|
-
url: `https://github.com/${
|
|
2810
|
+
url: `https://github.com/${pagesOwner}/${repo}`
|
|
1593
2811
|
},
|
|
1594
2812
|
publishedSiteUrl,
|
|
1595
2813
|
warnings: segments.length > 1 ? ["Only the first GitHub Pages path segment was used to infer the repository."] : []
|
|
@@ -1615,16 +2833,16 @@ function hashString(value) {
|
|
|
1615
2833
|
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
1616
2834
|
}
|
|
1617
2835
|
function hashFile(path) {
|
|
1618
|
-
if (!
|
|
2836
|
+
if (!existsSync4(path)) return null;
|
|
1619
2837
|
try {
|
|
1620
|
-
return createHash("sha256").update(
|
|
2838
|
+
return createHash("sha256").update(readFileSync5(path)).digest("hex");
|
|
1621
2839
|
} catch {
|
|
1622
2840
|
return null;
|
|
1623
2841
|
}
|
|
1624
2842
|
}
|
|
1625
2843
|
function provenanceEntry(projectRoot, relativePath) {
|
|
1626
|
-
const path =
|
|
1627
|
-
if (!
|
|
2844
|
+
const path = join4(projectRoot, relativePath);
|
|
2845
|
+
if (!existsSync4(path)) {
|
|
1628
2846
|
return {
|
|
1629
2847
|
path: relativePath,
|
|
1630
2848
|
present: false,
|
|
@@ -1646,8 +2864,8 @@ function provenanceEntry(projectRoot, relativePath) {
|
|
|
1646
2864
|
};
|
|
1647
2865
|
}
|
|
1648
2866
|
function provenanceForPath(projectRoot, path) {
|
|
1649
|
-
const rel = isAbsolute2(path) ?
|
|
1650
|
-
return provenanceEntry(projectRoot, rel && !rel.startsWith("..") ? rel :
|
|
2867
|
+
const rel = isAbsolute2(path) ? relative4(projectRoot, path).replace(/\\/g, "/") : path;
|
|
2868
|
+
return provenanceEntry(projectRoot, rel && !rel.startsWith("..") ? rel : basename2(path));
|
|
1651
2869
|
}
|
|
1652
2870
|
function redactEvidenceText(projectRoot, value) {
|
|
1653
2871
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
@@ -1660,6 +2878,116 @@ function redactEvidenceText(projectRoot, value) {
|
|
|
1660
2878
|
function redactEvidenceList(projectRoot, evidence) {
|
|
1661
2879
|
return evidence.map((entry) => redactEvidenceText(projectRoot, entry));
|
|
1662
2880
|
}
|
|
2881
|
+
function redactEvidenceValue(projectRoot, value) {
|
|
2882
|
+
if (typeof value === "string") return redactEvidenceText(projectRoot, value);
|
|
2883
|
+
if (Array.isArray(value)) return value.map((entry) => redactEvidenceValue(projectRoot, entry));
|
|
2884
|
+
if (value && typeof value === "object") {
|
|
2885
|
+
return Object.fromEntries(
|
|
2886
|
+
Object.entries(value).map(([key, entry]) => [
|
|
2887
|
+
key,
|
|
2888
|
+
redactEvidenceValue(projectRoot, entry)
|
|
2889
|
+
])
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
return value;
|
|
2893
|
+
}
|
|
2894
|
+
function redactRepairAction(projectRoot, repair) {
|
|
2895
|
+
if (!repair) return void 0;
|
|
2896
|
+
return {
|
|
2897
|
+
id: repair.id,
|
|
2898
|
+
...repair.payload ? { payload: redactEvidenceValue(projectRoot, repair.payload) } : {}
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
function evidenceRepairPlanAction(projectRoot, finding) {
|
|
2902
|
+
const repair = redactRepairAction(projectRoot, finding.repair);
|
|
2903
|
+
const repairId2 = repair?.id ?? "manual-repair";
|
|
2904
|
+
if (repairId2 === "regenerate-typed-graph" || finding.source === "graph") {
|
|
2905
|
+
return {
|
|
2906
|
+
id: repairId2,
|
|
2907
|
+
kind: "regenerate_artifact",
|
|
2908
|
+
target: ".decantr/graph",
|
|
2909
|
+
description: "Regenerate typed Contract graph artifacts from current project sources.",
|
|
2910
|
+
...repair?.payload ? { payload: repair.payload } : {}
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
if (repairId2 === "import-existing-component") {
|
|
2914
|
+
return {
|
|
2915
|
+
id: repairId2,
|
|
2916
|
+
kind: "replace_duplicate_with_import",
|
|
2917
|
+
target: finding.file ? redactEvidenceText(projectRoot, finding.file) : finding.target ? redactEvidenceText(projectRoot, finding.target) : null,
|
|
2918
|
+
description: "Remove the locally redeclared UI primitive and import the existing project-owned component.",
|
|
2919
|
+
...repair?.payload ? { payload: repair.payload } : {}
|
|
2920
|
+
};
|
|
2921
|
+
}
|
|
2922
|
+
if (repairId2 === "replace-raw-control-with-local-component") {
|
|
2923
|
+
return {
|
|
2924
|
+
id: repairId2,
|
|
2925
|
+
kind: "replace_raw_control_with_component",
|
|
2926
|
+
target: finding.file ? redactEvidenceText(projectRoot, finding.file) : finding.target ? redactEvidenceText(projectRoot, finding.target) : null,
|
|
2927
|
+
description: "Replace the raw JSX control with the existing project-owned primitive component.",
|
|
2928
|
+
...repair?.payload ? { payload: repair.payload } : {}
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
return {
|
|
2932
|
+
id: repairId2,
|
|
2933
|
+
kind: "manual_repair",
|
|
2934
|
+
target: finding.file ? redactEvidenceText(projectRoot, finding.file) : finding.target ? redactEvidenceText(projectRoot, finding.target) : null,
|
|
2935
|
+
description: finding.suggestedFix ?? finding.remediation.summary,
|
|
2936
|
+
...repair?.payload ? { payload: repair.payload } : {}
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
function evidenceRepairReadTargets(projectRoot, finding) {
|
|
2940
|
+
const targets = /* @__PURE__ */ new Set(["DECANTR.md", "decantr.essence.json"]);
|
|
2941
|
+
if (finding.source === "graph") {
|
|
2942
|
+
targets.add(".decantr/graph/graph.manifest.json");
|
|
2943
|
+
targets.add(".decantr/graph/graph.snapshot.json");
|
|
2944
|
+
targets.add(".decantr/graph/graph.diff.json");
|
|
2945
|
+
targets.add(".decantr/graph/snapshots/");
|
|
2946
|
+
}
|
|
2947
|
+
if (finding.source === "style-bridge") {
|
|
2948
|
+
targets.add(".decantr/style-bridge.json");
|
|
2949
|
+
}
|
|
2950
|
+
if (finding.source === "pack" || finding.source === "assertion") {
|
|
2951
|
+
targets.add(".decantr/context/pack-manifest.json");
|
|
2952
|
+
}
|
|
2953
|
+
if (finding.graph?.node_id) {
|
|
2954
|
+
targets.add(".decantr/graph/contract-capsule.json");
|
|
2955
|
+
}
|
|
2956
|
+
if (finding.file) targets.add(redactEvidenceText(projectRoot, finding.file));
|
|
2957
|
+
if (finding.target && !finding.target.startsWith("http")) {
|
|
2958
|
+
targets.add(redactEvidenceText(projectRoot, finding.target));
|
|
2959
|
+
}
|
|
2960
|
+
return [...targets];
|
|
2961
|
+
}
|
|
2962
|
+
function buildProjectHealthRepairPlan(projectRoot, finding) {
|
|
2963
|
+
return {
|
|
2964
|
+
id: `repair-plan:${finding.id}`,
|
|
2965
|
+
findingId: finding.id,
|
|
2966
|
+
diagnosticCode: finding.code ?? null,
|
|
2967
|
+
repairId: finding.repair?.id ?? null,
|
|
2968
|
+
severity: finding.severity,
|
|
2969
|
+
source: finding.source,
|
|
2970
|
+
category: finding.category,
|
|
2971
|
+
graphAnchor: finding.graph ?? null,
|
|
2972
|
+
actions: [evidenceRepairPlanAction(projectRoot, finding)],
|
|
2973
|
+
evidence: redactEvidenceList(projectRoot, finding.evidence).map((entry, index) => ({
|
|
2974
|
+
id: `evidence:${finding.id}:${index + 1}`,
|
|
2975
|
+
text: entry
|
|
2976
|
+
})),
|
|
2977
|
+
readTargets: evidenceRepairReadTargets(projectRoot, finding),
|
|
2978
|
+
preserve: [
|
|
2979
|
+
"existing framework, routing, and styling system",
|
|
2980
|
+
"existing production behavior unrelated to this finding",
|
|
2981
|
+
"accepted local law, style bridge mappings, and graph anchors"
|
|
2982
|
+
],
|
|
2983
|
+
avoid: [
|
|
2984
|
+
"rewriting unrelated routes",
|
|
2985
|
+
"replacing the app styling system",
|
|
2986
|
+
"regenerating Decantr artifacts unless the finding is about generated context or graph freshness"
|
|
2987
|
+
],
|
|
2988
|
+
commands: finding.remediation.commands
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
1663
2991
|
function assertion(input) {
|
|
1664
2992
|
return {
|
|
1665
2993
|
status: "passed",
|
|
@@ -1670,7 +2998,7 @@ function createContractAssertions(projectRoot, audit) {
|
|
|
1670
2998
|
const assertions = [];
|
|
1671
2999
|
const essence = audit?.essence;
|
|
1672
3000
|
const v4 = essence && isV4(essence) ? essence : null;
|
|
1673
|
-
const contextDir =
|
|
3001
|
+
const contextDir = join4(projectRoot, ".decantr", "context");
|
|
1674
3002
|
const packManifest = audit?.packManifest ?? null;
|
|
1675
3003
|
assertions.push(
|
|
1676
3004
|
assertion({
|
|
@@ -1680,7 +3008,7 @@ function createContractAssertions(projectRoot, audit) {
|
|
|
1680
3008
|
rule: "essence-present",
|
|
1681
3009
|
status: essence ? "passed" : "failed",
|
|
1682
3010
|
message: essence ? "Decantr Essence contract is present." : "No Decantr Essence contract was found.",
|
|
1683
|
-
evidence: [redactEvidenceText(projectRoot,
|
|
3011
|
+
evidence: [redactEvidenceText(projectRoot, join4(projectRoot, "decantr.essence.json"))],
|
|
1684
3012
|
suggestedFix: essence ? void 0 : "Run `decantr init` or restore decantr.essence.json."
|
|
1685
3013
|
})
|
|
1686
3014
|
);
|
|
@@ -1752,7 +3080,7 @@ function createContractAssertions(projectRoot, audit) {
|
|
|
1752
3080
|
rule: "pack-manifest-present",
|
|
1753
3081
|
status: packManifest ? "passed" : "failed",
|
|
1754
3082
|
message: packManifest ? "Compiled execution pack manifest is present." : "Compiled execution pack manifest is missing.",
|
|
1755
|
-
evidence: [redactEvidenceText(projectRoot,
|
|
3083
|
+
evidence: [redactEvidenceText(projectRoot, join4(contextDir, "pack-manifest.json"))],
|
|
1756
3084
|
suggestedFix: packManifest ? void 0 : "Run `decantr registry compile-packs decantr.essence.json --write-context` to hydrate the full context pack bundle."
|
|
1757
3085
|
})
|
|
1758
3086
|
);
|
|
@@ -1764,21 +3092,21 @@ function createContractAssertions(projectRoot, audit) {
|
|
|
1764
3092
|
rule: "review-pack-present",
|
|
1765
3093
|
status: audit?.reviewPack ? "passed" : "failed",
|
|
1766
3094
|
message: audit?.reviewPack ? "Compiled review pack is present." : "Compiled review pack is missing.",
|
|
1767
|
-
evidence: [redactEvidenceText(projectRoot,
|
|
3095
|
+
evidence: [redactEvidenceText(projectRoot, join4(contextDir, "review-pack.json"))],
|
|
1768
3096
|
suggestedFix: audit?.reviewPack ? void 0 : "Run `decantr registry compile-packs decantr.essence.json --write-context` so critique has the compiled review contract."
|
|
1769
3097
|
})
|
|
1770
3098
|
);
|
|
1771
|
-
const tokensPath =
|
|
3099
|
+
const tokensPath = join4(projectRoot, "src", "styles", "tokens.css");
|
|
1772
3100
|
assertions.push(
|
|
1773
3101
|
assertion({
|
|
1774
3102
|
id: "contract.design-token.tokens-file",
|
|
1775
3103
|
category: "design-token",
|
|
1776
|
-
severity:
|
|
3104
|
+
severity: existsSync4(tokensPath) ? "info" : "warn",
|
|
1777
3105
|
rule: "tokens-file-present",
|
|
1778
|
-
status:
|
|
1779
|
-
message:
|
|
3106
|
+
status: existsSync4(tokensPath) ? "passed" : "failed",
|
|
3107
|
+
message: existsSync4(tokensPath) ? "Decantr token CSS file is present." : "Decantr token CSS file was not found.",
|
|
1780
3108
|
evidence: [redactEvidenceText(projectRoot, tokensPath)],
|
|
1781
|
-
suggestedFix:
|
|
3109
|
+
suggestedFix: existsSync4(tokensPath) ? void 0 : "Run `decantr refresh` or map Decantr tokens into the existing styling system."
|
|
1782
3110
|
})
|
|
1783
3111
|
);
|
|
1784
3112
|
return assertions;
|
|
@@ -1797,7 +3125,7 @@ function createEvidenceBundle(input) {
|
|
|
1797
3125
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1798
3126
|
project: {
|
|
1799
3127
|
id: projectId,
|
|
1800
|
-
rootLabel:
|
|
3128
|
+
rootLabel: basename2(input.projectRoot) || "project"
|
|
1801
3129
|
},
|
|
1802
3130
|
toolchain: {
|
|
1803
3131
|
verifierVersion: input.verifierVersion ?? null
|
|
@@ -1826,12 +3154,17 @@ function createEvidenceBundle(input) {
|
|
|
1826
3154
|
essence: provenanceEntry(input.projectRoot, "decantr.essence.json"),
|
|
1827
3155
|
packManifest: provenanceEntry(input.projectRoot, ".decantr/context/pack-manifest.json"),
|
|
1828
3156
|
reviewPack: provenanceEntry(input.projectRoot, ".decantr/context/review-pack.json"),
|
|
3157
|
+
graphSnapshot: provenanceEntry(input.projectRoot, ".decantr/graph/graph.snapshot.json"),
|
|
3158
|
+
graphManifest: provenanceEntry(input.projectRoot, ".decantr/graph/graph.manifest.json"),
|
|
3159
|
+
graphDiff: provenanceEntry(input.projectRoot, ".decantr/graph/graph.diff.json"),
|
|
3160
|
+
contractCapsule: provenanceEntry(input.projectRoot, ".decantr/graph/contract-capsule.json"),
|
|
1829
3161
|
...input.workspaceConfigPath ? { workspaceConfig: provenanceForPath(input.projectRoot, input.workspaceConfigPath) } : {},
|
|
1830
3162
|
...input.designTokensPath ? { designTokens: provenanceForPath(input.projectRoot, input.designTokensPath) } : {}
|
|
1831
3163
|
},
|
|
1832
3164
|
assertions,
|
|
1833
3165
|
findings: input.report.findings.map((finding) => ({
|
|
1834
3166
|
id: finding.id,
|
|
3167
|
+
code: finding.code,
|
|
1835
3168
|
source: finding.source,
|
|
1836
3169
|
category: finding.category,
|
|
1837
3170
|
severity: finding.severity,
|
|
@@ -1840,6 +3173,9 @@ function createEvidenceBundle(input) {
|
|
|
1840
3173
|
target: finding.target ? redactEvidenceText(input.projectRoot, finding.target) : void 0,
|
|
1841
3174
|
rule: finding.rule,
|
|
1842
3175
|
suggestedFix: finding.suggestedFix,
|
|
3176
|
+
graph: finding.graph,
|
|
3177
|
+
repair: redactRepairAction(input.projectRoot, finding.repair),
|
|
3178
|
+
repairPlan: buildProjectHealthRepairPlan(input.projectRoot, finding),
|
|
1843
3179
|
remediationSummary: finding.remediation.summary,
|
|
1844
3180
|
commands: finding.remediation.commands,
|
|
1845
3181
|
promptCommand: `decantr health --prompt ${finding.id}`
|
|
@@ -1883,21 +3219,21 @@ function scoreRatio(numerator, denominator) {
|
|
|
1883
3219
|
return Math.min(5, Math.max(1, Math.round(numerator / denominator * 5)));
|
|
1884
3220
|
}
|
|
1885
3221
|
function readJsonIfExists(path) {
|
|
1886
|
-
if (!
|
|
3222
|
+
if (!existsSync4(path)) return null;
|
|
1887
3223
|
try {
|
|
1888
|
-
return JSON.parse(
|
|
3224
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
1889
3225
|
} catch {
|
|
1890
3226
|
return null;
|
|
1891
3227
|
}
|
|
1892
3228
|
}
|
|
1893
3229
|
function loadReviewPack(projectRoot) {
|
|
1894
3230
|
return readJsonIfExists(
|
|
1895
|
-
|
|
3231
|
+
join4(projectRoot, ".decantr", "context", "review-pack.json")
|
|
1896
3232
|
);
|
|
1897
3233
|
}
|
|
1898
3234
|
function loadPackManifest(projectRoot) {
|
|
1899
3235
|
return readJsonIfExists(
|
|
1900
|
-
|
|
3236
|
+
join4(projectRoot, ".decantr", "context", "pack-manifest.json")
|
|
1901
3237
|
);
|
|
1902
3238
|
}
|
|
1903
3239
|
function collectPackManifestReferences(packManifest) {
|
|
@@ -1946,14 +3282,14 @@ function collectPackManifestReferences(packManifest) {
|
|
|
1946
3282
|
}
|
|
1947
3283
|
function collectMissingPackManifestFiles(projectRoot, packManifest = loadPackManifest(projectRoot)) {
|
|
1948
3284
|
if (!packManifest) return [];
|
|
1949
|
-
const contextDir =
|
|
3285
|
+
const contextDir = join4(projectRoot, ".decantr", "context");
|
|
1950
3286
|
const missing = [];
|
|
1951
3287
|
for (const reference of collectPackManifestReferences(packManifest)) {
|
|
1952
3288
|
for (const field of ["markdown", "json"]) {
|
|
1953
3289
|
const fileName = reference[field];
|
|
1954
3290
|
if (!fileName) continue;
|
|
1955
|
-
const absolutePath =
|
|
1956
|
-
if (
|
|
3291
|
+
const absolutePath = join4(contextDir, fileName);
|
|
3292
|
+
if (existsSync4(absolutePath)) continue;
|
|
1957
3293
|
missing.push({
|
|
1958
3294
|
entryId: reference.entryId,
|
|
1959
3295
|
kind: reference.kind,
|
|
@@ -1967,13 +3303,13 @@ function collectMissingPackManifestFiles(projectRoot, packManifest = loadPackMan
|
|
|
1967
3303
|
}
|
|
1968
3304
|
function readTextIfExists(path) {
|
|
1969
3305
|
try {
|
|
1970
|
-
return
|
|
3306
|
+
return existsSync4(path) ? readFileSync5(path, "utf-8") : "";
|
|
1971
3307
|
} catch {
|
|
1972
3308
|
return "";
|
|
1973
3309
|
}
|
|
1974
3310
|
}
|
|
1975
3311
|
function readProjectAdoptionMode(projectRoot) {
|
|
1976
|
-
const projectJson = readJsonIfExists(
|
|
3312
|
+
const projectJson = readJsonIfExists(join4(projectRoot, ".decantr", "project.json"));
|
|
1977
3313
|
const adoptionMode = projectJson?.initialized?.adoptionMode;
|
|
1978
3314
|
return typeof adoptionMode === "string" ? adoptionMode : null;
|
|
1979
3315
|
}
|
|
@@ -2059,7 +3395,7 @@ function collectProjectSourceFiles(projectRoot) {
|
|
|
2059
3395
|
"hooks",
|
|
2060
3396
|
"providers",
|
|
2061
3397
|
"server"
|
|
2062
|
-
].map((dir) =>
|
|
3398
|
+
].map((dir) => join4(projectRoot, dir)).filter((dir) => existsSync4(dir));
|
|
2063
3399
|
const rootFileCandidates = [
|
|
2064
3400
|
"middleware.ts",
|
|
2065
3401
|
"middleware.tsx",
|
|
@@ -2073,7 +3409,7 @@ function collectProjectSourceFiles(projectRoot) {
|
|
|
2073
3409
|
"proxy.jsx",
|
|
2074
3410
|
"proxy.mts",
|
|
2075
3411
|
"proxy.cts"
|
|
2076
|
-
].map((file) =>
|
|
3412
|
+
].map((file) => join4(projectRoot, file)).filter((file) => existsSync4(file));
|
|
2077
3413
|
const files = /* @__PURE__ */ new Set();
|
|
2078
3414
|
const ignoredDirNames = /* @__PURE__ */ new Set([
|
|
2079
3415
|
"node_modules",
|
|
@@ -2084,9 +3420,9 @@ function collectProjectSourceFiles(projectRoot) {
|
|
|
2084
3420
|
"coverage"
|
|
2085
3421
|
]);
|
|
2086
3422
|
const walk = (dir) => {
|
|
2087
|
-
for (const entry of
|
|
3423
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
2088
3424
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
2089
|
-
const absolutePath =
|
|
3425
|
+
const absolutePath = join4(dir, entry.name);
|
|
2090
3426
|
if (entry.isDirectory()) {
|
|
2091
3427
|
walk(absolutePath);
|
|
2092
3428
|
continue;
|
|
@@ -2116,8 +3452,8 @@ function buildSourceAuditEvidence(summary, bucket, label) {
|
|
|
2116
3452
|
function isAuditableStyleFile(filePath) {
|
|
2117
3453
|
return /\.css$/i.test(filePath);
|
|
2118
3454
|
}
|
|
2119
|
-
function
|
|
2120
|
-
const candidates = ["src", "app", "styles"].map((dir) =>
|
|
3455
|
+
function collectProjectStyleFiles2(projectRoot) {
|
|
3456
|
+
const candidates = ["src", "app", "styles"].map((dir) => join4(projectRoot, dir)).filter((dir) => existsSync4(dir));
|
|
2121
3457
|
const files = /* @__PURE__ */ new Set();
|
|
2122
3458
|
const ignoredDirNames = /* @__PURE__ */ new Set([
|
|
2123
3459
|
"node_modules",
|
|
@@ -2128,9 +3464,9 @@ function collectProjectStyleFiles(projectRoot) {
|
|
|
2128
3464
|
"coverage"
|
|
2129
3465
|
]);
|
|
2130
3466
|
const walk = (dir) => {
|
|
2131
|
-
for (const entry of
|
|
3467
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
2132
3468
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
2133
|
-
const absolutePath =
|
|
3469
|
+
const absolutePath = join4(dir, entry.name);
|
|
2134
3470
|
if (entry.isDirectory()) {
|
|
2135
3471
|
walk(absolutePath);
|
|
2136
3472
|
continue;
|
|
@@ -2158,15 +3494,15 @@ function countReducedMotionSignals(code) {
|
|
|
2158
3494
|
return patterns.reduce((count, pattern) => count + (pattern.test(code) ? 1 : 0), 0);
|
|
2159
3495
|
}
|
|
2160
3496
|
function hasModuleDirective(filePath, code, directive) {
|
|
2161
|
-
const sourceFile =
|
|
3497
|
+
const sourceFile = ts3.createSourceFile(
|
|
2162
3498
|
filePath,
|
|
2163
3499
|
code,
|
|
2164
|
-
|
|
3500
|
+
ts3.ScriptTarget.Latest,
|
|
2165
3501
|
true,
|
|
2166
3502
|
getScriptKind(filePath)
|
|
2167
3503
|
);
|
|
2168
3504
|
for (const statement of sourceFile.statements) {
|
|
2169
|
-
if (
|
|
3505
|
+
if (ts3.isExpressionStatement(statement) && ts3.isStringLiteralLike(statement.expression)) {
|
|
2170
3506
|
if (statement.expression.text === directive) {
|
|
2171
3507
|
return true;
|
|
2172
3508
|
}
|
|
@@ -2182,20 +3518,20 @@ function importDeclarationHasRuntimeBinding(statement) {
|
|
|
2182
3518
|
if (clause.isTypeOnly) return false;
|
|
2183
3519
|
if (clause.name) return true;
|
|
2184
3520
|
if (!clause.namedBindings) return true;
|
|
2185
|
-
if (
|
|
3521
|
+
if (ts3.isNamespaceImport(clause.namedBindings)) return true;
|
|
2186
3522
|
return clause.namedBindings.elements.some((element) => !element.isTypeOnly);
|
|
2187
3523
|
}
|
|
2188
3524
|
function collectRuntimeImportSpecifiers(entry) {
|
|
2189
|
-
const sourceFile =
|
|
3525
|
+
const sourceFile = ts3.createSourceFile(
|
|
2190
3526
|
entry.relativePath,
|
|
2191
3527
|
entry.code,
|
|
2192
|
-
|
|
3528
|
+
ts3.ScriptTarget.Latest,
|
|
2193
3529
|
true,
|
|
2194
3530
|
getScriptKind(entry.relativePath)
|
|
2195
3531
|
);
|
|
2196
3532
|
const specifiers = [];
|
|
2197
3533
|
for (const statement of sourceFile.statements) {
|
|
2198
|
-
if (
|
|
3534
|
+
if (ts3.isImportDeclaration(statement) && ts3.isStringLiteralLike(statement.moduleSpecifier) && importDeclarationHasRuntimeBinding(statement)) {
|
|
2199
3535
|
specifiers.push(statement.moduleSpecifier.text);
|
|
2200
3536
|
}
|
|
2201
3537
|
}
|
|
@@ -2204,7 +3540,7 @@ function collectRuntimeImportSpecifiers(entry) {
|
|
|
2204
3540
|
function resolveSourceImportTarget(projectRoot, sourceAbsolutePath, specifier) {
|
|
2205
3541
|
let basePath = null;
|
|
2206
3542
|
if (specifier.startsWith("@/")) {
|
|
2207
|
-
basePath =
|
|
3543
|
+
basePath = join4(projectRoot, "src", specifier.slice(2));
|
|
2208
3544
|
} else if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2209
3545
|
basePath = resolve(dirname(sourceAbsolutePath), specifier);
|
|
2210
3546
|
}
|
|
@@ -2217,14 +3553,14 @@ function resolveSourceImportTarget(projectRoot, sourceAbsolutePath, specifier) {
|
|
|
2217
3553
|
`${basePath}.jsx`,
|
|
2218
3554
|
`${basePath}.mts`,
|
|
2219
3555
|
`${basePath}.cts`,
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
3556
|
+
join4(basePath, "index.ts"),
|
|
3557
|
+
join4(basePath, "index.tsx"),
|
|
3558
|
+
join4(basePath, "index.js"),
|
|
3559
|
+
join4(basePath, "index.jsx")
|
|
2224
3560
|
];
|
|
2225
3561
|
for (const candidate of candidates) {
|
|
2226
|
-
if (
|
|
2227
|
-
return
|
|
3562
|
+
if (existsSync4(candidate) && isAuditableSourceFile(candidate)) {
|
|
3563
|
+
return relative4(projectRoot, candidate) || candidate;
|
|
2228
3564
|
}
|
|
2229
3565
|
}
|
|
2230
3566
|
return null;
|
|
@@ -2269,12 +3605,11 @@ function isClientAuthHeaderSource(relativePath, code, clientReachableFiles) {
|
|
|
2269
3605
|
}
|
|
2270
3606
|
return /(?:^|\/)(?:components|routes|pages|hooks|providers)\//i.test(normalized);
|
|
2271
3607
|
}
|
|
2272
|
-
function auditProjectSourceTree(projectRoot) {
|
|
2273
|
-
const sourceFiles = collectProjectSourceFiles(projectRoot);
|
|
3608
|
+
function auditProjectSourceTree(projectRoot, sourceFiles = collectProjectSourceFiles(projectRoot)) {
|
|
2274
3609
|
const sourceEntries = sourceFiles.map((sourceFile) => ({
|
|
2275
3610
|
absolutePath: sourceFile,
|
|
2276
|
-
relativePath:
|
|
2277
|
-
code:
|
|
3611
|
+
relativePath: relative4(projectRoot, sourceFile) || sourceFile,
|
|
3612
|
+
code: readFileSync5(sourceFile, "utf-8")
|
|
2278
3613
|
})).filter((entry) => !isNonProductionSourceAuditFile(entry.relativePath));
|
|
2279
3614
|
const clientReachableFiles = collectClientReachableSourceFiles(projectRoot, sourceEntries);
|
|
2280
3615
|
const summary = {
|
|
@@ -2580,15 +3915,15 @@ function auditProjectSourceTree(projectRoot) {
|
|
|
2580
3915
|
return summary;
|
|
2581
3916
|
}
|
|
2582
3917
|
function auditProjectStyleContracts(projectRoot) {
|
|
2583
|
-
const styleFiles =
|
|
3918
|
+
const styleFiles = collectProjectStyleFiles2(projectRoot);
|
|
2584
3919
|
const summary = {
|
|
2585
3920
|
filesChecked: styleFiles.length,
|
|
2586
3921
|
focusVisibleSignals: createSourceAuditBucket(),
|
|
2587
3922
|
reducedMotionSignals: createSourceAuditBucket()
|
|
2588
3923
|
};
|
|
2589
3924
|
for (const styleFile of styleFiles) {
|
|
2590
|
-
const relativePath =
|
|
2591
|
-
const css =
|
|
3925
|
+
const relativePath = relative4(projectRoot, styleFile) || styleFile;
|
|
3926
|
+
const css = readFileSync5(styleFile, "utf-8");
|
|
2592
3927
|
recordSourceAudit(summary.focusVisibleSignals, relativePath, countFocusVisibleSignals(css));
|
|
2593
3928
|
recordSourceAudit(summary.reducedMotionSignals, relativePath, countReducedMotionSignals(css));
|
|
2594
3929
|
}
|
|
@@ -2597,15 +3932,15 @@ function auditProjectStyleContracts(projectRoot) {
|
|
|
2597
3932
|
function buildRegistryContext(projectRoot) {
|
|
2598
3933
|
const themeRegistry = /* @__PURE__ */ new Map();
|
|
2599
3934
|
const patternRegistry = /* @__PURE__ */ new Map();
|
|
2600
|
-
const cacheDir =
|
|
2601
|
-
const customDir =
|
|
2602
|
-
const cachedThemesDir =
|
|
3935
|
+
const cacheDir = join4(projectRoot, ".decantr", "cache");
|
|
3936
|
+
const customDir = join4(projectRoot, ".decantr", "custom");
|
|
3937
|
+
const cachedThemesDir = join4(cacheDir, "@official", "themes");
|
|
2603
3938
|
try {
|
|
2604
|
-
if (
|
|
2605
|
-
for (const file of
|
|
3939
|
+
if (existsSync4(cachedThemesDir)) {
|
|
3940
|
+
for (const file of readdirSync4(cachedThemesDir).filter(
|
|
2606
3941
|
(name) => name.endsWith(".json") && name !== "index.json"
|
|
2607
3942
|
)) {
|
|
2608
|
-
const data = JSON.parse(
|
|
3943
|
+
const data = JSON.parse(readFileSync5(join4(cachedThemesDir, file), "utf-8"));
|
|
2609
3944
|
if (data.id && !themeRegistry.has(data.id)) {
|
|
2610
3945
|
themeRegistry.set(data.id, { modes: data.modes || ["light", "dark"] });
|
|
2611
3946
|
}
|
|
@@ -2613,11 +3948,11 @@ function buildRegistryContext(projectRoot) {
|
|
|
2613
3948
|
}
|
|
2614
3949
|
} catch {
|
|
2615
3950
|
}
|
|
2616
|
-
const customThemesDir =
|
|
3951
|
+
const customThemesDir = join4(customDir, "themes");
|
|
2617
3952
|
try {
|
|
2618
|
-
if (
|
|
2619
|
-
for (const file of
|
|
2620
|
-
const data = JSON.parse(
|
|
3953
|
+
if (existsSync4(customThemesDir)) {
|
|
3954
|
+
for (const file of readdirSync4(customThemesDir).filter((name) => name.endsWith(".json"))) {
|
|
3955
|
+
const data = JSON.parse(readFileSync5(join4(customThemesDir, file), "utf-8"));
|
|
2621
3956
|
if (data.id) {
|
|
2622
3957
|
themeRegistry.set(`custom:${data.id}`, { modes: data.modes || ["light", "dark"] });
|
|
2623
3958
|
}
|
|
@@ -2625,13 +3960,13 @@ function buildRegistryContext(projectRoot) {
|
|
|
2625
3960
|
}
|
|
2626
3961
|
} catch {
|
|
2627
3962
|
}
|
|
2628
|
-
const cachedPatternsDir =
|
|
3963
|
+
const cachedPatternsDir = join4(cacheDir, "@official", "patterns");
|
|
2629
3964
|
try {
|
|
2630
|
-
if (
|
|
2631
|
-
for (const file of
|
|
3965
|
+
if (existsSync4(cachedPatternsDir)) {
|
|
3966
|
+
for (const file of readdirSync4(cachedPatternsDir).filter(
|
|
2632
3967
|
(name) => name.endsWith(".json") && name !== "index.json"
|
|
2633
3968
|
)) {
|
|
2634
|
-
const data = JSON.parse(
|
|
3969
|
+
const data = JSON.parse(readFileSync5(join4(cachedPatternsDir, file), "utf-8"));
|
|
2635
3970
|
if (data.id && !patternRegistry.has(data.id)) {
|
|
2636
3971
|
patternRegistry.set(data.id, data);
|
|
2637
3972
|
}
|
|
@@ -2945,8 +4280,8 @@ function extractFailedRouteDocumentHardening(runtimeAudit) {
|
|
|
2945
4280
|
return runtimeAudit.failures.filter((failure) => failure.startsWith("route-document-hardening-failed:")).map((failure) => normalizeRouteHint2(failure.slice("route-document-hardening-failed:".length))).filter(Boolean);
|
|
2946
4281
|
}
|
|
2947
4282
|
function appendRuntimeAuditFindings(findings, runtimeAudit, projectRoot, topology, sourceAudit) {
|
|
2948
|
-
const distPath =
|
|
2949
|
-
const indexPath =
|
|
4283
|
+
const distPath = join4(projectRoot, "dist");
|
|
4284
|
+
const indexPath = join4(distPath, "index.html");
|
|
2950
4285
|
const isFrameworkBuildOutput = runtimeAudit.failures.includes("next-build-output");
|
|
2951
4286
|
if (!runtimeAudit.distPresent) {
|
|
2952
4287
|
findings.push(
|
|
@@ -4590,6 +5925,88 @@ function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack, a
|
|
|
4590
5925
|
);
|
|
4591
5926
|
}
|
|
4592
5927
|
}
|
|
5928
|
+
function appendComponentReuseFindings(findings, audit) {
|
|
5929
|
+
for (const finding of audit.findings.slice(0, 8)) {
|
|
5930
|
+
findings.push(
|
|
5931
|
+
makeFinding({
|
|
5932
|
+
id: COMPONENT_REUSE_RULE_ID,
|
|
5933
|
+
code: "COMP001",
|
|
5934
|
+
category: "Component Reuse",
|
|
5935
|
+
severity: "warn",
|
|
5936
|
+
message: `${finding.name} is reimplemented locally even though a reusable ${finding.name} component already exists.`,
|
|
5937
|
+
evidence: finding.evidence,
|
|
5938
|
+
target: finding.name,
|
|
5939
|
+
file: finding.file,
|
|
5940
|
+
rule: COMPONENT_REUSE_RULE_ID,
|
|
5941
|
+
suggestedFix: `Import ${finding.name} from ${finding.canonicalFile} instead of redefining it in ${finding.file}.`,
|
|
5942
|
+
repair: {
|
|
5943
|
+
id: "import-existing-component",
|
|
5944
|
+
payload: {
|
|
5945
|
+
component: finding.name,
|
|
5946
|
+
file: finding.file,
|
|
5947
|
+
canonical_file: finding.canonicalFile
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
})
|
|
5951
|
+
);
|
|
5952
|
+
}
|
|
5953
|
+
for (const finding of audit.rawControlFindings.slice(0, 8)) {
|
|
5954
|
+
findings.push(
|
|
5955
|
+
makeFinding({
|
|
5956
|
+
id: RAW_CONTROL_REUSE_RULE_ID,
|
|
5957
|
+
code: "COMP010",
|
|
5958
|
+
category: "Component Reuse",
|
|
5959
|
+
severity: "warn",
|
|
5960
|
+
message: `${finding.file} renders raw <${finding.element}> even though the project has a reusable ${finding.component} component.`,
|
|
5961
|
+
evidence: finding.evidence,
|
|
5962
|
+
target: finding.component,
|
|
5963
|
+
file: finding.file,
|
|
5964
|
+
rule: RAW_CONTROL_REUSE_RULE_ID,
|
|
5965
|
+
suggestedFix: `Use ${finding.component} from ${finding.canonicalFile} instead of raw <${finding.element}> in ${finding.file}.`,
|
|
5966
|
+
repair: {
|
|
5967
|
+
id: "replace-raw-control-with-local-component",
|
|
5968
|
+
payload: {
|
|
5969
|
+
component: finding.component,
|
|
5970
|
+
element: finding.element,
|
|
5971
|
+
file: finding.file,
|
|
5972
|
+
canonical_file: finding.canonicalFile
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
})
|
|
5976
|
+
);
|
|
5977
|
+
}
|
|
5978
|
+
}
|
|
5979
|
+
function appendStyleBridgeDriftFindings(findings, audit) {
|
|
5980
|
+
for (const finding of audit.findings.slice(0, 8)) {
|
|
5981
|
+
findings.push(
|
|
5982
|
+
makeFinding({
|
|
5983
|
+
id: STYLE_BRIDGE_ARBITRARY_VALUE_RULE_ID,
|
|
5984
|
+
code: "TOKEN010",
|
|
5985
|
+
category: "Style Bridge",
|
|
5986
|
+
severity: "warn",
|
|
5987
|
+
message: `${finding.file} uses arbitrary styling even though an accepted style bridge defines project-owned token/class authority.`,
|
|
5988
|
+
evidence: finding.evidence,
|
|
5989
|
+
target: finding.value,
|
|
5990
|
+
file: finding.file,
|
|
5991
|
+
rule: STYLE_BRIDGE_ARBITRARY_VALUE_RULE_ID,
|
|
5992
|
+
suggestedFix: "Replace the arbitrary Tailwind value with an accepted project token or class from .decantr/style-bridge.json, or update the style bridge if this value is now approved design authority.",
|
|
5993
|
+
repair: {
|
|
5994
|
+
id: "replace-arbitrary-style-with-bridge-token",
|
|
5995
|
+
payload: {
|
|
5996
|
+
file: finding.file,
|
|
5997
|
+
line: finding.line,
|
|
5998
|
+
value: finding.value,
|
|
5999
|
+
source: finding.source,
|
|
6000
|
+
property: finding.property,
|
|
6001
|
+
bridge_mappings: finding.bridgeMappingIds,
|
|
6002
|
+
token_hints: finding.tokenHints,
|
|
6003
|
+
class_hints: finding.classHints
|
|
6004
|
+
}
|
|
6005
|
+
}
|
|
6006
|
+
})
|
|
6007
|
+
);
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
4593
6010
|
function appendStyleContractFindings(findings, styleAudit, essence) {
|
|
4594
6011
|
if (essenceRequiresFocusVisible(essence) && styleAudit.focusVisibleSignals.count === 0) {
|
|
4595
6012
|
findings.push(
|
|
@@ -4625,7 +6042,7 @@ function appendStyleContractFindings(findings, styleAudit, essence) {
|
|
|
4625
6042
|
}
|
|
4626
6043
|
}
|
|
4627
6044
|
async function auditProject(projectRoot) {
|
|
4628
|
-
const essencePath =
|
|
6045
|
+
const essencePath = join4(projectRoot, "decantr.essence.json");
|
|
4629
6046
|
const findings = [];
|
|
4630
6047
|
const reviewPack = loadReviewPack(projectRoot);
|
|
4631
6048
|
const packManifest = loadPackManifest(projectRoot);
|
|
@@ -4633,7 +6050,7 @@ async function auditProject(projectRoot) {
|
|
|
4633
6050
|
const packHydrationOptional = adoptionMode === "contract-only" || adoptionMode === "style-bridge";
|
|
4634
6051
|
const packHydrationSeverity = packHydrationOptional ? "info" : "warn";
|
|
4635
6052
|
const runtimeAudit = emptyRuntimeAudit();
|
|
4636
|
-
if (!
|
|
6053
|
+
if (!existsSync4(essencePath)) {
|
|
4637
6054
|
findings.push(
|
|
4638
6055
|
makeFinding({
|
|
4639
6056
|
id: "essence-missing",
|
|
@@ -4668,7 +6085,7 @@ async function auditProject(projectRoot) {
|
|
|
4668
6085
|
}
|
|
4669
6086
|
let essence = null;
|
|
4670
6087
|
try {
|
|
4671
|
-
essence = JSON.parse(
|
|
6088
|
+
essence = JSON.parse(readFileSync5(essencePath, "utf-8"));
|
|
4672
6089
|
} catch (error) {
|
|
4673
6090
|
findings.push(
|
|
4674
6091
|
makeFinding({
|
|
@@ -4711,7 +6128,7 @@ async function auditProject(projectRoot) {
|
|
|
4711
6128
|
category: "Execution Packs",
|
|
4712
6129
|
severity: packHydrationSeverity,
|
|
4713
6130
|
message: packHydrationOptional ? "Compiled execution pack manifest is not hydrated yet; this is optional for contract-only/style-bridge Brownfield adoption." : "Compiled execution pack manifest is missing.",
|
|
4714
|
-
evidence: [
|
|
6131
|
+
evidence: [join4(projectRoot, ".decantr", "context", "pack-manifest.json")],
|
|
4715
6132
|
suggestedFix: packHydrationOptional ? "Optional: run `decantr registry compile-packs decantr.essence.json --write-context` when you want hosted page packs and review packs for richer assistant context." : "Run `decantr registry compile-packs decantr.essence.json --write-context` to hydrate scaffold, review, mutation, section, and page packs."
|
|
4716
6133
|
})
|
|
4717
6134
|
);
|
|
@@ -4775,12 +6192,15 @@ async function auditProject(projectRoot) {
|
|
|
4775
6192
|
category: "Review Contract",
|
|
4776
6193
|
severity: packHydrationSeverity,
|
|
4777
6194
|
message: packHydrationOptional ? "The compiled review pack file is not hydrated yet; contract-only/style-bridge Brownfield projects can continue without it." : "The compiled review pack file is missing.",
|
|
4778
|
-
evidence: [
|
|
6195
|
+
evidence: [join4(projectRoot, ".decantr", "context", "review-pack.json")],
|
|
4779
6196
|
suggestedFix: packHydrationOptional ? "Optional: hydrate hosted packs later if you want critique consumers to anchor findings to the compiled review contract." : "Hydrate the full hosted context bundle with `decantr registry compile-packs decantr.essence.json --write-context` so critique consumers can anchor findings to the compiled review contract."
|
|
4780
6197
|
})
|
|
4781
6198
|
);
|
|
4782
6199
|
}
|
|
4783
|
-
const
|
|
6200
|
+
const projectSourceFiles = collectProjectSourceFiles(projectRoot);
|
|
6201
|
+
const sourceAudit = auditProjectSourceTree(projectRoot, projectSourceFiles);
|
|
6202
|
+
const componentReuseAudit = auditComponentReuse(projectRoot, projectSourceFiles);
|
|
6203
|
+
const styleBridgeDriftAudit = auditStyleBridgeDrift(projectRoot, projectSourceFiles);
|
|
4784
6204
|
const styleAudit = auditProjectStyleContracts(projectRoot);
|
|
4785
6205
|
const checkedRuntimeAudit = await runRuntimeAudit(projectRoot, essence);
|
|
4786
6206
|
appendRuntimeAuditFindings(
|
|
@@ -4791,6 +6211,8 @@ async function auditProject(projectRoot) {
|
|
|
4791
6211
|
sourceAudit
|
|
4792
6212
|
);
|
|
4793
6213
|
appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack, adoptionMode);
|
|
6214
|
+
appendComponentReuseFindings(findings, componentReuseAudit);
|
|
6215
|
+
appendStyleBridgeDriftFindings(findings, styleBridgeDriftAudit);
|
|
4794
6216
|
appendStyleContractFindings(findings, styleAudit, essence);
|
|
4795
6217
|
const summary = {
|
|
4796
6218
|
errorCount: findings.filter((finding) => finding.severity === "error").length,
|
|
@@ -4916,74 +6338,74 @@ function countKeyboardShortcutSignals(code) {
|
|
|
4916
6338
|
return matches.length;
|
|
4917
6339
|
}
|
|
4918
6340
|
function getScriptKind(filePath) {
|
|
4919
|
-
switch (
|
|
6341
|
+
switch (extname4(filePath).toLowerCase()) {
|
|
4920
6342
|
case ".tsx":
|
|
4921
|
-
return
|
|
6343
|
+
return ts3.ScriptKind.TSX;
|
|
4922
6344
|
case ".jsx":
|
|
4923
|
-
return
|
|
6345
|
+
return ts3.ScriptKind.JSX;
|
|
4924
6346
|
case ".ts":
|
|
4925
|
-
return
|
|
6347
|
+
return ts3.ScriptKind.TS;
|
|
4926
6348
|
case ".js":
|
|
4927
|
-
return
|
|
6349
|
+
return ts3.ScriptKind.JS;
|
|
4928
6350
|
default:
|
|
4929
|
-
return
|
|
6351
|
+
return ts3.ScriptKind.TSX;
|
|
4930
6352
|
}
|
|
4931
6353
|
}
|
|
4932
6354
|
function isPropertyNamed(node, ...names) {
|
|
4933
6355
|
if (!node) return false;
|
|
4934
|
-
if (
|
|
6356
|
+
if (ts3.isIdentifier(node)) {
|
|
4935
6357
|
return names.includes(node.text);
|
|
4936
6358
|
}
|
|
4937
|
-
if (
|
|
6359
|
+
if (ts3.isPrivateIdentifier(node)) {
|
|
4938
6360
|
return names.includes(node.text);
|
|
4939
6361
|
}
|
|
4940
|
-
if (
|
|
6362
|
+
if (ts3.isStringLiteral(node)) {
|
|
4941
6363
|
return names.includes(node.text);
|
|
4942
6364
|
}
|
|
4943
6365
|
return false;
|
|
4944
6366
|
}
|
|
4945
6367
|
function getJsxAttribute(attributes, ...names) {
|
|
4946
6368
|
return attributes.properties.find(
|
|
4947
|
-
(property) =>
|
|
6369
|
+
(property) => ts3.isJsxAttribute(property) && isPropertyNamed(property.name, ...names)
|
|
4948
6370
|
);
|
|
4949
6371
|
}
|
|
4950
6372
|
function getJsxTagName(node) {
|
|
4951
|
-
if (
|
|
6373
|
+
if (ts3.isIdentifier(node.tagName)) {
|
|
4952
6374
|
return node.tagName.text;
|
|
4953
6375
|
}
|
|
4954
|
-
if (
|
|
6376
|
+
if (ts3.isPropertyAccessExpression(node.tagName)) {
|
|
4955
6377
|
return node.tagName.name.text;
|
|
4956
6378
|
}
|
|
4957
6379
|
return null;
|
|
4958
6380
|
}
|
|
4959
6381
|
function getJsxAttributeLiteralValue(attribute) {
|
|
4960
6382
|
if (!attribute?.initializer) return null;
|
|
4961
|
-
if (
|
|
6383
|
+
if (ts3.isStringLiteral(attribute.initializer)) {
|
|
4962
6384
|
return attribute.initializer.text;
|
|
4963
6385
|
}
|
|
4964
|
-
if (
|
|
6386
|
+
if (ts3.isJsxExpression(attribute.initializer)) {
|
|
4965
6387
|
const expression = attribute.initializer.expression;
|
|
4966
6388
|
if (!expression) return "";
|
|
4967
|
-
if (
|
|
6389
|
+
if (ts3.isStringLiteral(expression) || ts3.isNoSubstitutionTemplateLiteral(expression)) {
|
|
4968
6390
|
return expression.text;
|
|
4969
6391
|
}
|
|
4970
6392
|
}
|
|
4971
6393
|
return null;
|
|
4972
6394
|
}
|
|
4973
6395
|
function getJsxTextContent(node) {
|
|
4974
|
-
if (
|
|
6396
|
+
if (ts3.isJsxText(node)) {
|
|
4975
6397
|
return node.getText();
|
|
4976
6398
|
}
|
|
4977
|
-
if (
|
|
6399
|
+
if (ts3.isStringLiteral(node) || ts3.isNoSubstitutionTemplateLiteral(node)) {
|
|
4978
6400
|
return node.text;
|
|
4979
6401
|
}
|
|
4980
|
-
if (
|
|
6402
|
+
if (ts3.isJsxExpression(node)) {
|
|
4981
6403
|
return node.expression ? getJsxTextContent(node.expression) : "";
|
|
4982
6404
|
}
|
|
4983
|
-
if (
|
|
6405
|
+
if (ts3.isJsxSelfClosingElement(node) || ts3.isJsxOpeningElement(node)) {
|
|
4984
6406
|
return "";
|
|
4985
6407
|
}
|
|
4986
|
-
if (
|
|
6408
|
+
if (ts3.isJsxElement(node) || ts3.isJsxFragment(node)) {
|
|
4987
6409
|
return node.children.map((child) => getJsxTextContent(child)).join("");
|
|
4988
6410
|
}
|
|
4989
6411
|
let text = "";
|
|
@@ -5003,7 +6425,7 @@ function isNonSemanticInteractiveTag(tagName) {
|
|
|
5003
6425
|
function collectLabelForIds(root) {
|
|
5004
6426
|
const ids = /* @__PURE__ */ new Set();
|
|
5005
6427
|
const walk = (node) => {
|
|
5006
|
-
if (
|
|
6428
|
+
if (ts3.isJsxSelfClosingElement(node) || ts3.isJsxOpeningElement(node)) {
|
|
5007
6429
|
const tagName = getJsxTagName(node);
|
|
5008
6430
|
if (tagName === "label") {
|
|
5009
6431
|
const htmlForValue = getJsxAttributeLiteralValue(
|
|
@@ -5014,7 +6436,7 @@ function collectLabelForIds(root) {
|
|
|
5014
6436
|
}
|
|
5015
6437
|
}
|
|
5016
6438
|
}
|
|
5017
|
-
|
|
6439
|
+
ts3.forEachChild(node, walk);
|
|
5018
6440
|
};
|
|
5019
6441
|
walk(root);
|
|
5020
6442
|
return ids;
|
|
@@ -5022,7 +6444,7 @@ function collectLabelForIds(root) {
|
|
|
5022
6444
|
function isWrappedInJsxLabel(node) {
|
|
5023
6445
|
let current = node.parent;
|
|
5024
6446
|
while (current) {
|
|
5025
|
-
if (
|
|
6447
|
+
if (ts3.isJsxElement(current) && getJsxTagName(current.openingElement) === "label") {
|
|
5026
6448
|
return true;
|
|
5027
6449
|
}
|
|
5028
6450
|
current = current.parent;
|
|
@@ -5032,7 +6454,7 @@ function isWrappedInJsxLabel(node) {
|
|
|
5032
6454
|
function hasAncestorJsxTag(node, tagName) {
|
|
5033
6455
|
let current = node.parent;
|
|
5034
6456
|
while (current) {
|
|
5035
|
-
if (
|
|
6457
|
+
if (ts3.isJsxElement(current) && getJsxTagName(current.openingElement) === tagName) {
|
|
5036
6458
|
return true;
|
|
5037
6459
|
}
|
|
5038
6460
|
current = current.parent;
|
|
@@ -5110,9 +6532,9 @@ function hasNavigationLabel(attributes) {
|
|
|
5110
6532
|
}
|
|
5111
6533
|
function getJsxAttributeOwner(attribute) {
|
|
5112
6534
|
const attributes = attribute.parent;
|
|
5113
|
-
if (!attributes || !
|
|
6535
|
+
if (!attributes || !ts3.isJsxAttributes(attributes)) return null;
|
|
5114
6536
|
const owner = attributes.parent;
|
|
5115
|
-
if (
|
|
6537
|
+
if (ts3.isJsxOpeningElement(owner) || ts3.isJsxSelfClosingElement(owner)) {
|
|
5116
6538
|
return owner;
|
|
5117
6539
|
}
|
|
5118
6540
|
return null;
|
|
@@ -5199,14 +6621,14 @@ function jsxTreeContainsAuthInput(node) {
|
|
|
5199
6621
|
let found = false;
|
|
5200
6622
|
const visit = (current) => {
|
|
5201
6623
|
if (found) return;
|
|
5202
|
-
if (
|
|
6624
|
+
if (ts3.isJsxSelfClosingElement(current)) {
|
|
5203
6625
|
const tagName = getJsxTagName(current);
|
|
5204
6626
|
if (tagName === "input" && isAuthLikeInputAttributes(current.attributes)) {
|
|
5205
6627
|
found = true;
|
|
5206
6628
|
}
|
|
5207
6629
|
return;
|
|
5208
6630
|
}
|
|
5209
|
-
if (
|
|
6631
|
+
if (ts3.isJsxElement(current)) {
|
|
5210
6632
|
const tagName = getJsxTagName(current.openingElement);
|
|
5211
6633
|
if (tagName === "input" && isAuthLikeInputAttributes(current.openingElement.attributes)) {
|
|
5212
6634
|
found = true;
|
|
@@ -5218,7 +6640,7 @@ function jsxTreeContainsAuthInput(node) {
|
|
|
5218
6640
|
}
|
|
5219
6641
|
return;
|
|
5220
6642
|
}
|
|
5221
|
-
if (
|
|
6643
|
+
if (ts3.isJsxFragment(current)) {
|
|
5222
6644
|
for (const child of current.children) {
|
|
5223
6645
|
visit(child);
|
|
5224
6646
|
if (found) return;
|
|
@@ -5242,7 +6664,7 @@ function jsxTreeHasSubmitControl(node) {
|
|
|
5242
6664
|
let found = false;
|
|
5243
6665
|
const visit = (current) => {
|
|
5244
6666
|
if (found) return;
|
|
5245
|
-
if (
|
|
6667
|
+
if (ts3.isJsxSelfClosingElement(current)) {
|
|
5246
6668
|
const tagName = getJsxTagName(current);
|
|
5247
6669
|
if (tagName === "button") {
|
|
5248
6670
|
const typeValue = getJsxAttributeLiteralValue(getJsxAttribute(current.attributes, "type"))?.trim().toLowerCase();
|
|
@@ -5258,7 +6680,7 @@ function jsxTreeHasSubmitControl(node) {
|
|
|
5258
6680
|
}
|
|
5259
6681
|
return;
|
|
5260
6682
|
}
|
|
5261
|
-
if (
|
|
6683
|
+
if (ts3.isJsxElement(current)) {
|
|
5262
6684
|
const tagName = getJsxTagName(current.openingElement);
|
|
5263
6685
|
if (tagName === "button") {
|
|
5264
6686
|
const typeValue = getJsxAttributeLiteralValue(
|
|
@@ -5282,7 +6704,7 @@ function jsxTreeHasSubmitControl(node) {
|
|
|
5282
6704
|
}
|
|
5283
6705
|
return;
|
|
5284
6706
|
}
|
|
5285
|
-
if (
|
|
6707
|
+
if (ts3.isJsxFragment(current)) {
|
|
5286
6708
|
for (const child of current.children) {
|
|
5287
6709
|
visit(child);
|
|
5288
6710
|
if (found) return;
|
|
@@ -5299,14 +6721,14 @@ function jsxTreeHasTag(node, tagNames) {
|
|
|
5299
6721
|
const wanted = new Set(tagNames);
|
|
5300
6722
|
const visit = (current) => {
|
|
5301
6723
|
if (found) return;
|
|
5302
|
-
if (
|
|
6724
|
+
if (ts3.isJsxSelfClosingElement(current)) {
|
|
5303
6725
|
const tagName = getJsxTagName(current);
|
|
5304
6726
|
if (tagName && wanted.has(tagName)) {
|
|
5305
6727
|
found = true;
|
|
5306
6728
|
}
|
|
5307
6729
|
return;
|
|
5308
6730
|
}
|
|
5309
|
-
if (
|
|
6731
|
+
if (ts3.isJsxElement(current)) {
|
|
5310
6732
|
const tagName = getJsxTagName(current.openingElement);
|
|
5311
6733
|
if (tagName && wanted.has(tagName)) {
|
|
5312
6734
|
found = true;
|
|
@@ -5318,7 +6740,7 @@ function jsxTreeHasTag(node, tagNames) {
|
|
|
5318
6740
|
}
|
|
5319
6741
|
return;
|
|
5320
6742
|
}
|
|
5321
|
-
if (
|
|
6743
|
+
if (ts3.isJsxFragment(current)) {
|
|
5322
6744
|
for (const child of current.children) {
|
|
5323
6745
|
visit(child);
|
|
5324
6746
|
if (found) return;
|
|
@@ -5335,47 +6757,47 @@ function hasAuthFormWithoutSubmitControl(node) {
|
|
|
5335
6757
|
}
|
|
5336
6758
|
function getExpressionLiteralValue(expression) {
|
|
5337
6759
|
if (!expression) return null;
|
|
5338
|
-
if (
|
|
6760
|
+
if (ts3.isStringLiteral(expression) || ts3.isNoSubstitutionTemplateLiteral(expression)) {
|
|
5339
6761
|
return expression.text;
|
|
5340
6762
|
}
|
|
5341
|
-
if (
|
|
6763
|
+
if (ts3.isParenthesizedExpression(expression)) {
|
|
5342
6764
|
return getExpressionLiteralValue(expression.expression);
|
|
5343
6765
|
}
|
|
5344
|
-
if (
|
|
6766
|
+
if (ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression)) {
|
|
5345
6767
|
return getExpressionLiteralValue(expression.expression);
|
|
5346
6768
|
}
|
|
5347
6769
|
return null;
|
|
5348
6770
|
}
|
|
5349
6771
|
function getObjectLiteralStringPropertyValue(expression, ...propertyNames) {
|
|
5350
|
-
if (!expression || !
|
|
6772
|
+
if (!expression || !ts3.isObjectLiteralExpression(expression)) return null;
|
|
5351
6773
|
for (const property of expression.properties) {
|
|
5352
|
-
if (!
|
|
5353
|
-
const
|
|
5354
|
-
if (!
|
|
6774
|
+
if (!ts3.isPropertyAssignment(property)) continue;
|
|
6775
|
+
const propertyName2 = ts3.isIdentifier(property.name) ? property.name.text : ts3.isStringLiteral(property.name) ? property.name.text : null;
|
|
6776
|
+
if (!propertyName2 || !propertyNames.includes(propertyName2)) continue;
|
|
5355
6777
|
const value = getExpressionLiteralValue(property.initializer);
|
|
5356
6778
|
if (value) return value;
|
|
5357
6779
|
}
|
|
5358
6780
|
return null;
|
|
5359
6781
|
}
|
|
5360
6782
|
function getObjectLiteralBooleanPropertyValue(expression, ...propertyNames) {
|
|
5361
|
-
if (!expression || !
|
|
6783
|
+
if (!expression || !ts3.isObjectLiteralExpression(expression)) return null;
|
|
5362
6784
|
for (const property of expression.properties) {
|
|
5363
|
-
if (!
|
|
5364
|
-
const
|
|
5365
|
-
if (!
|
|
5366
|
-
if (property.initializer.kind ===
|
|
5367
|
-
if (property.initializer.kind ===
|
|
6785
|
+
if (!ts3.isPropertyAssignment(property)) continue;
|
|
6786
|
+
const propertyName2 = ts3.isIdentifier(property.name) ? property.name.text : ts3.isStringLiteral(property.name) ? property.name.text : null;
|
|
6787
|
+
if (!propertyName2 || !propertyNames.includes(propertyName2)) continue;
|
|
6788
|
+
if (property.initializer.kind === ts3.SyntaxKind.TrueKeyword) return true;
|
|
6789
|
+
if (property.initializer.kind === ts3.SyntaxKind.FalseKeyword) return false;
|
|
5368
6790
|
}
|
|
5369
6791
|
return null;
|
|
5370
6792
|
}
|
|
5371
6793
|
function collectNamedFunctionLikeDeclarations(root) {
|
|
5372
6794
|
const declarations = /* @__PURE__ */ new Map();
|
|
5373
6795
|
const visit = (node) => {
|
|
5374
|
-
if (
|
|
6796
|
+
if (ts3.isFunctionDeclaration(node) && node.name && ts3.isIdentifier(node.name)) {
|
|
5375
6797
|
declarations.set(node.name.text, node);
|
|
5376
6798
|
}
|
|
5377
|
-
if (
|
|
5378
|
-
if (
|
|
6799
|
+
if (ts3.isVariableDeclaration(node) && ts3.isIdentifier(node.name) && node.initializer) {
|
|
6800
|
+
if (ts3.isArrowFunction(node.initializer) || ts3.isFunctionExpression(node.initializer)) {
|
|
5379
6801
|
declarations.set(node.name.text, node.initializer);
|
|
5380
6802
|
}
|
|
5381
6803
|
}
|
|
@@ -5395,12 +6817,12 @@ function getCachedNamedFunctionLikeDeclarations(sourceFile) {
|
|
|
5395
6817
|
function collectNamedExpressionInitializers(root) {
|
|
5396
6818
|
const initializers = /* @__PURE__ */ new Map();
|
|
5397
6819
|
const visit = (node) => {
|
|
5398
|
-
if (
|
|
6820
|
+
if (ts3.isVariableDeclaration(node) && ts3.isIdentifier(node.name) && node.initializer) {
|
|
5399
6821
|
initializers.set(node.name.text, node.initializer);
|
|
5400
6822
|
}
|
|
5401
|
-
if (
|
|
6823
|
+
if (ts3.isVariableDeclaration(node) && ts3.isArrayBindingPattern(node.name) && node.initializer) {
|
|
5402
6824
|
const firstElement = node.name.elements[0];
|
|
5403
|
-
if (firstElement && !
|
|
6825
|
+
if (firstElement && !ts3.isOmittedExpression(firstElement) && ts3.isBindingElement(firstElement) && ts3.isIdentifier(firstElement.name)) {
|
|
5404
6826
|
initializers.set(firstElement.name.text, node.initializer);
|
|
5405
6827
|
}
|
|
5406
6828
|
}
|
|
@@ -5411,7 +6833,7 @@ function collectNamedExpressionInitializers(root) {
|
|
|
5411
6833
|
}
|
|
5412
6834
|
function getPropertyNameText(name) {
|
|
5413
6835
|
if (!name) return null;
|
|
5414
|
-
if (
|
|
6836
|
+
if (ts3.isIdentifier(name) || ts3.isStringLiteral(name) || ts3.isNumericLiteral(name)) {
|
|
5415
6837
|
return name.text;
|
|
5416
6838
|
}
|
|
5417
6839
|
return null;
|
|
@@ -5421,23 +6843,23 @@ function collectNamedPropertyAliases(root) {
|
|
|
5421
6843
|
const collectFromBindingPattern = (pattern, initializer, propertyPath) => {
|
|
5422
6844
|
for (const element of pattern.elements) {
|
|
5423
6845
|
if (element.dotDotDotToken) continue;
|
|
5424
|
-
const
|
|
5425
|
-
if (!
|
|
5426
|
-
const nextPath = [...propertyPath,
|
|
5427
|
-
if (
|
|
5428
|
-
aliases.set(element.name.text, { initializer, propertyName, propertyPath: nextPath });
|
|
6846
|
+
const propertyName2 = getPropertyNameText(element.propertyName) ?? getPropertyNameText(element.name);
|
|
6847
|
+
if (!propertyName2) continue;
|
|
6848
|
+
const nextPath = [...propertyPath, propertyName2];
|
|
6849
|
+
if (ts3.isIdentifier(element.name)) {
|
|
6850
|
+
aliases.set(element.name.text, { initializer, propertyName: propertyName2, propertyPath: nextPath });
|
|
5429
6851
|
continue;
|
|
5430
6852
|
}
|
|
5431
|
-
if (
|
|
6853
|
+
if (ts3.isObjectBindingPattern(element.name)) {
|
|
5432
6854
|
collectFromBindingPattern(element.name, initializer, nextPath);
|
|
5433
6855
|
}
|
|
5434
6856
|
}
|
|
5435
6857
|
};
|
|
5436
6858
|
const visit = (node) => {
|
|
5437
|
-
if (
|
|
6859
|
+
if (ts3.isVariableDeclaration(node) && ts3.isObjectBindingPattern(node.name) && node.initializer) {
|
|
5438
6860
|
collectFromBindingPattern(node.name, node.initializer, []);
|
|
5439
6861
|
}
|
|
5440
|
-
if (
|
|
6862
|
+
if (ts3.isParameter(node) && ts3.isObjectBindingPattern(node.name)) {
|
|
5441
6863
|
collectFromBindingPattern(node.name, void 0, []);
|
|
5442
6864
|
}
|
|
5443
6865
|
node.forEachChild(visit);
|
|
@@ -5447,28 +6869,28 @@ function collectNamedPropertyAliases(root) {
|
|
|
5447
6869
|
}
|
|
5448
6870
|
function resolveFunctionLikeHandler(expression, namedFunctions) {
|
|
5449
6871
|
if (!expression) return null;
|
|
5450
|
-
if (
|
|
6872
|
+
if (ts3.isArrowFunction(expression) || ts3.isFunctionExpression(expression)) {
|
|
5451
6873
|
return expression;
|
|
5452
6874
|
}
|
|
5453
|
-
if (
|
|
6875
|
+
if (ts3.isIdentifier(expression)) {
|
|
5454
6876
|
return namedFunctions.get(expression.text) ?? null;
|
|
5455
6877
|
}
|
|
5456
6878
|
return null;
|
|
5457
6879
|
}
|
|
5458
6880
|
function getFunctionLikeReturnExpressions(node) {
|
|
5459
|
-
if (
|
|
6881
|
+
if (ts3.isArrowFunction(node) && !ts3.isBlock(node.body)) {
|
|
5460
6882
|
return [node.body];
|
|
5461
6883
|
}
|
|
5462
6884
|
const body = node.body;
|
|
5463
|
-
if (!body || !
|
|
6885
|
+
if (!body || !ts3.isBlock(body)) {
|
|
5464
6886
|
return [];
|
|
5465
6887
|
}
|
|
5466
6888
|
const expressions = [];
|
|
5467
6889
|
const visit = (current) => {
|
|
5468
|
-
if (current !== body && (
|
|
6890
|
+
if (current !== body && (ts3.isFunctionDeclaration(current) || ts3.isFunctionExpression(current) || ts3.isArrowFunction(current) || ts3.isMethodDeclaration(current) || ts3.isGetAccessorDeclaration(current) || ts3.isSetAccessorDeclaration(current) || ts3.isConstructorDeclaration(current))) {
|
|
5469
6891
|
return;
|
|
5470
6892
|
}
|
|
5471
|
-
if (
|
|
6893
|
+
if (ts3.isReturnStatement(current) && current.expression) {
|
|
5472
6894
|
expressions.push(current.expression);
|
|
5473
6895
|
return;
|
|
5474
6896
|
}
|
|
@@ -5484,7 +6906,7 @@ function bindFunctionLikeArguments(functionLike, args, namedExpressions) {
|
|
|
5484
6906
|
const boundExpressions = new Map(namedExpressions);
|
|
5485
6907
|
functionLike.parameters.forEach((parameter, index) => {
|
|
5486
6908
|
const argument = args[index];
|
|
5487
|
-
if (!argument || !
|
|
6909
|
+
if (!argument || !ts3.isIdentifier(parameter.name)) return;
|
|
5488
6910
|
boundExpressions.set(parameter.name.text, argument);
|
|
5489
6911
|
});
|
|
5490
6912
|
return boundExpressions;
|
|
@@ -5496,12 +6918,12 @@ function getMemberAccessPropertyName(expression) {
|
|
|
5496
6918
|
}
|
|
5497
6919
|
return getExpressionLiteralValue(expression.argumentExpression);
|
|
5498
6920
|
}
|
|
5499
|
-
function getObjectLiteralPropertyExpression(objectLiteral,
|
|
6921
|
+
function getObjectLiteralPropertyExpression(objectLiteral, propertyName2, namedExpressions) {
|
|
5500
6922
|
for (const property of objectLiteral.properties) {
|
|
5501
|
-
if (
|
|
6923
|
+
if (ts3.isPropertyAssignment(property) && getPropertyNameText(property.name) === propertyName2) {
|
|
5502
6924
|
return property.initializer;
|
|
5503
6925
|
}
|
|
5504
|
-
if (
|
|
6926
|
+
if (ts3.isShorthandPropertyAssignment(property) && property.name.text === propertyName2) {
|
|
5505
6927
|
return namedExpressions.get(property.name.text) ?? null;
|
|
5506
6928
|
}
|
|
5507
6929
|
}
|
|
@@ -5520,10 +6942,10 @@ function resolveObjectLiteralExpressionAtPropertyPath(expression, sourceFile, pr
|
|
|
5520
6942
|
depth + 1
|
|
5521
6943
|
);
|
|
5522
6944
|
if (!resolvedObjectLiteral) return null;
|
|
5523
|
-
for (const
|
|
6945
|
+
for (const propertyName2 of propertyPath) {
|
|
5524
6946
|
const propertyExpression = getObjectLiteralPropertyExpression(
|
|
5525
6947
|
resolvedObjectLiteral.objectLiteral,
|
|
5526
|
-
|
|
6948
|
+
propertyName2,
|
|
5527
6949
|
resolvedObjectLiteral.namedExpressions
|
|
5528
6950
|
);
|
|
5529
6951
|
if (!propertyExpression) return null;
|
|
@@ -5567,7 +6989,7 @@ function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile,
|
|
|
5567
6989
|
function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set(), depth = 0) {
|
|
5568
6990
|
if (!expression) return null;
|
|
5569
6991
|
if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
|
|
5570
|
-
if (
|
|
6992
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
5571
6993
|
return resolveObjectLiteralExpression(
|
|
5572
6994
|
expression.expression,
|
|
5573
6995
|
sourceFile,
|
|
@@ -5578,10 +7000,10 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
|
|
|
5578
7000
|
depth + 1
|
|
5579
7001
|
);
|
|
5580
7002
|
}
|
|
5581
|
-
if (
|
|
7003
|
+
if (ts3.isObjectLiteralExpression(expression)) {
|
|
5582
7004
|
return { objectLiteral: expression, namedExpressions };
|
|
5583
7005
|
}
|
|
5584
|
-
if (
|
|
7006
|
+
if (ts3.isIdentifier(expression)) {
|
|
5585
7007
|
if (seenIdentifiers.has(expression.text)) return null;
|
|
5586
7008
|
const initializer = namedExpressions.get(expression.text);
|
|
5587
7009
|
if (initializer) {
|
|
@@ -5637,12 +7059,12 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
|
|
|
5637
7059
|
);
|
|
5638
7060
|
}
|
|
5639
7061
|
if (isMemberAccessExpression(expression)) {
|
|
5640
|
-
const
|
|
5641
|
-
if (!
|
|
7062
|
+
const propertyName2 = getMemberAccessPropertyName(expression);
|
|
7063
|
+
if (!propertyName2) return null;
|
|
5642
7064
|
return resolveObjectLiteralExpressionAtPropertyPath(
|
|
5643
7065
|
expression.expression,
|
|
5644
7066
|
sourceFile,
|
|
5645
|
-
[
|
|
7067
|
+
[propertyName2],
|
|
5646
7068
|
namedExpressions,
|
|
5647
7069
|
namedPropertyAliases,
|
|
5648
7070
|
seenIdentifiers,
|
|
@@ -5652,15 +7074,15 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
|
|
|
5652
7074
|
}
|
|
5653
7075
|
return null;
|
|
5654
7076
|
}
|
|
5655
|
-
function findFunctionLikeOnObjectLiteral(objectLiteral,
|
|
7077
|
+
function findFunctionLikeOnObjectLiteral(objectLiteral, propertyName2, namedFunctions) {
|
|
5656
7078
|
for (const property of objectLiteral.properties) {
|
|
5657
|
-
if (
|
|
7079
|
+
if (ts3.isMethodDeclaration(property) && getPropertyNameText(property.name) === propertyName2) {
|
|
5658
7080
|
return property;
|
|
5659
7081
|
}
|
|
5660
|
-
if (
|
|
7082
|
+
if (ts3.isPropertyAssignment(property) && getPropertyNameText(property.name) === propertyName2) {
|
|
5661
7083
|
return resolveFunctionLikeHandler(property.initializer, namedFunctions);
|
|
5662
7084
|
}
|
|
5663
|
-
if (
|
|
7085
|
+
if (ts3.isShorthandPropertyAssignment(property) && property.name.text === propertyName2) {
|
|
5664
7086
|
return namedFunctions.get(property.name.text) ?? null;
|
|
5665
7087
|
}
|
|
5666
7088
|
}
|
|
@@ -5695,7 +7117,7 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
|
|
|
5695
7117
|
if (directFunctionLike) {
|
|
5696
7118
|
return { functionLike: directFunctionLike, namedExpressions };
|
|
5697
7119
|
}
|
|
5698
|
-
if (
|
|
7120
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
5699
7121
|
return resolveTrackedOpenRedirectFunctionLike(
|
|
5700
7122
|
expression.expression,
|
|
5701
7123
|
sourceFile,
|
|
@@ -5728,7 +7150,7 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
|
|
|
5728
7150
|
depth + 1
|
|
5729
7151
|
);
|
|
5730
7152
|
}
|
|
5731
|
-
if (
|
|
7153
|
+
if (ts3.isIdentifier(expression)) {
|
|
5732
7154
|
if (seenIdentifiers.has(expression.text)) return null;
|
|
5733
7155
|
const initializer = namedExpressions.get(expression.text);
|
|
5734
7156
|
if (initializer) {
|
|
@@ -5768,8 +7190,8 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
|
|
|
5768
7190
|
if (!isMemberAccessExpression(expression)) {
|
|
5769
7191
|
return null;
|
|
5770
7192
|
}
|
|
5771
|
-
const
|
|
5772
|
-
if (!
|
|
7193
|
+
const propertyName2 = getMemberAccessPropertyName(expression);
|
|
7194
|
+
if (!propertyName2) return null;
|
|
5773
7195
|
const resolvedObjectLiteral = resolveObjectLiteralExpression(
|
|
5774
7196
|
expression.expression,
|
|
5775
7197
|
sourceFile,
|
|
@@ -5782,7 +7204,7 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
|
|
|
5782
7204
|
if (!resolvedObjectLiteral) return null;
|
|
5783
7205
|
const functionLike = findFunctionLikeOnObjectLiteral(
|
|
5784
7206
|
resolvedObjectLiteral.objectLiteral,
|
|
5785
|
-
|
|
7207
|
+
propertyName2,
|
|
5786
7208
|
namedFunctions
|
|
5787
7209
|
);
|
|
5788
7210
|
return functionLike ? { functionLike, namedExpressions: resolvedObjectLiteral.namedExpressions } : null;
|
|
@@ -5791,16 +7213,16 @@ function functionLikeReferencesOrigin(node) {
|
|
|
5791
7213
|
let found = false;
|
|
5792
7214
|
const visit = (current) => {
|
|
5793
7215
|
if (found) return;
|
|
5794
|
-
if (
|
|
7216
|
+
if (ts3.isPropertyAccessExpression(current) && isPropertyNamed(current.name, "origin")) {
|
|
5795
7217
|
found = true;
|
|
5796
7218
|
return;
|
|
5797
7219
|
}
|
|
5798
|
-
if (
|
|
7220
|
+
if (ts3.isIdentifier(current) && current.text === "origin") {
|
|
5799
7221
|
found = true;
|
|
5800
7222
|
return;
|
|
5801
7223
|
}
|
|
5802
|
-
if (
|
|
5803
|
-
if (
|
|
7224
|
+
if (ts3.isBindingElement(current)) {
|
|
7225
|
+
if (ts3.isIdentifier(current.name) && current.name.text === "origin") {
|
|
5804
7226
|
found = true;
|
|
5805
7227
|
return;
|
|
5806
7228
|
}
|
|
@@ -5815,13 +7237,13 @@ function functionLikeReferencesOrigin(node) {
|
|
|
5815
7237
|
return found;
|
|
5816
7238
|
}
|
|
5817
7239
|
function isAddEventListenerCall(node) {
|
|
5818
|
-
return
|
|
7240
|
+
return ts3.isIdentifier(node.expression) && node.expression.text === "addEventListener" || ts3.isPropertyAccessExpression(node.expression) && isPropertyNamed(node.expression.name, "addEventListener");
|
|
5819
7241
|
}
|
|
5820
7242
|
function isCookieMutationSetCall(node) {
|
|
5821
|
-
if (!
|
|
7243
|
+
if (!ts3.isPropertyAccessExpression(node.expression) || !isPropertyNamed(node.expression.name, "set")) {
|
|
5822
7244
|
return false;
|
|
5823
7245
|
}
|
|
5824
|
-
return
|
|
7246
|
+
return ts3.isIdentifier(node.expression.expression) && ["Cookies", "cookieStore", "cookies"].includes(node.expression.expression.text) || ts3.isPropertyAccessExpression(node.expression.expression) && isPropertyNamed(node.expression.expression.name, "cookies", "cookieStore");
|
|
5825
7247
|
}
|
|
5826
7248
|
function expressionLooksLikeAuthCookieName(node, sourceFile) {
|
|
5827
7249
|
if (!node) return false;
|
|
@@ -5829,17 +7251,17 @@ function expressionLooksLikeAuthCookieName(node, sourceFile) {
|
|
|
5829
7251
|
return hasAuthCredentialText(node, sourceFile);
|
|
5830
7252
|
}
|
|
5831
7253
|
function objectLiteralLooksLikeAuthCookieConfig(expression, sourceFile) {
|
|
5832
|
-
if (!expression || !
|
|
7254
|
+
if (!expression || !ts3.isObjectLiteralExpression(expression)) return false;
|
|
5833
7255
|
for (const property of expression.properties) {
|
|
5834
|
-
if (!
|
|
5835
|
-
const
|
|
5836
|
-
if (!
|
|
7256
|
+
if (!ts3.isPropertyAssignment(property)) continue;
|
|
7257
|
+
const propertyName2 = ts3.isIdentifier(property.name) ? property.name.text : ts3.isStringLiteral(property.name) ? property.name.text : null;
|
|
7258
|
+
if (!propertyName2 || !["name", "key", "cookie"].includes(propertyName2)) continue;
|
|
5837
7259
|
return expressionLooksLikeAuthCookieName(property.initializer, sourceFile);
|
|
5838
7260
|
}
|
|
5839
7261
|
return false;
|
|
5840
7262
|
}
|
|
5841
7263
|
function hasExplicitAuthCookieHardening(expression) {
|
|
5842
|
-
if (!expression || !
|
|
7264
|
+
if (!expression || !ts3.isObjectLiteralExpression(expression)) return false;
|
|
5843
7265
|
const httpOnly = getObjectLiteralBooleanPropertyValue(expression, "httpOnly", "http_only");
|
|
5844
7266
|
const secure = getObjectLiteralBooleanPropertyValue(expression, "secure");
|
|
5845
7267
|
const sameSite = getObjectLiteralStringPropertyValue(expression, "sameSite", "same_site");
|
|
@@ -5859,8 +7281,8 @@ function collectCookieHeaderStrings(expression) {
|
|
|
5859
7281
|
if (!expression) return [];
|
|
5860
7282
|
const literal = getExpressionLiteralValue(expression);
|
|
5861
7283
|
if (literal !== null) return [literal];
|
|
5862
|
-
if (
|
|
5863
|
-
return expression.elements.map((element) =>
|
|
7284
|
+
if (ts3.isArrayLiteralExpression(expression)) {
|
|
7285
|
+
return expression.elements.map((element) => ts3.isExpression(element) ? getExpressionLiteralValue(element) : null).filter((value) => value !== null);
|
|
5864
7286
|
}
|
|
5865
7287
|
return [];
|
|
5866
7288
|
}
|
|
@@ -5868,16 +7290,16 @@ function isInsecureTransportUrl(value) {
|
|
|
5868
7290
|
return typeof value === "string" && /^(?:http|ws):\/\//i.test(value.trim());
|
|
5869
7291
|
}
|
|
5870
7292
|
function isPropertyLikeAccessExpression(node) {
|
|
5871
|
-
return Boolean(node && (
|
|
7293
|
+
return Boolean(node && (ts3.isPropertyAccessExpression(node) || ts3.isPropertyAccessChain(node)));
|
|
5872
7294
|
}
|
|
5873
7295
|
function isElementLikeAccessExpression(node) {
|
|
5874
|
-
return Boolean(node && (
|
|
7296
|
+
return Boolean(node && (ts3.isElementAccessExpression(node) || ts3.isElementAccessChain(node)));
|
|
5875
7297
|
}
|
|
5876
7298
|
function isMemberAccessExpression(node) {
|
|
5877
7299
|
return isPropertyLikeAccessExpression(node) || isElementLikeAccessExpression(node);
|
|
5878
7300
|
}
|
|
5879
7301
|
function isCallLikeExpression(node) {
|
|
5880
|
-
return Boolean(node && (
|
|
7302
|
+
return Boolean(node && (ts3.isCallExpression(node) || ts3.isCallChain(node)));
|
|
5881
7303
|
}
|
|
5882
7304
|
function isMemberAccessNamed(node, ...names) {
|
|
5883
7305
|
if (!node) return false;
|
|
@@ -5890,19 +7312,19 @@ function isMemberAccessNamed(node, ...names) {
|
|
|
5890
7312
|
return false;
|
|
5891
7313
|
}
|
|
5892
7314
|
function isLocationObjectExpression(expression) {
|
|
5893
|
-
return
|
|
7315
|
+
return ts3.isIdentifier(expression) && expression.text === "location" || (ts3.isPropertyAccessExpression(expression) || ts3.isElementAccessExpression(expression)) && ts3.isIdentifier(expression.expression) && ["window", "globalThis", "document", "self", "parent", "top"].includes(
|
|
5894
7316
|
expression.expression.text
|
|
5895
7317
|
) && isMemberAccessNamed(expression, "location");
|
|
5896
7318
|
}
|
|
5897
7319
|
function isLocationAssignmentTarget(expression) {
|
|
5898
|
-
return isLocationObjectExpression(expression) || (
|
|
7320
|
+
return isLocationObjectExpression(expression) || (ts3.isPropertyAccessExpression(expression) || ts3.isElementAccessExpression(expression)) && isLocationObjectExpression(expression.expression) && isMemberAccessNamed(expression, "href");
|
|
5899
7321
|
}
|
|
5900
7322
|
function isFetchLikeCall(node) {
|
|
5901
|
-
return
|
|
7323
|
+
return ts3.isIdentifier(node.expression) && node.expression.text === "fetch" || ts3.isPropertyAccessExpression(node.expression) && isPropertyNamed(node.expression.name, "fetch");
|
|
5902
7324
|
}
|
|
5903
7325
|
function isAxiosLikeCall(node) {
|
|
5904
|
-
if (!
|
|
5905
|
-
return
|
|
7326
|
+
if (!ts3.isPropertyAccessExpression(node.expression)) return false;
|
|
7327
|
+
return ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "axios" && isPropertyNamed(
|
|
5906
7328
|
node.expression.name,
|
|
5907
7329
|
"get",
|
|
5908
7330
|
"post",
|
|
@@ -5915,10 +7337,10 @@ function isAxiosLikeCall(node) {
|
|
|
5915
7337
|
);
|
|
5916
7338
|
}
|
|
5917
7339
|
function isAxiosConfigCall(node) {
|
|
5918
|
-
return
|
|
7340
|
+
return ts3.isIdentifier(node.expression) && node.expression.text === "axios";
|
|
5919
7341
|
}
|
|
5920
7342
|
function isRealtimeTransportConstructor(node) {
|
|
5921
|
-
return
|
|
7343
|
+
return ts3.isIdentifier(node.expression) && (node.expression.text === "WebSocket" || node.expression.text === "EventSource");
|
|
5922
7344
|
}
|
|
5923
7345
|
function hasPlaceholderNavigationTarget(attributes) {
|
|
5924
7346
|
const targetValue = getJsxAttributeLiteralValue(getJsxAttribute(attributes, "href", "to"));
|
|
@@ -5985,40 +7407,40 @@ function getSkipNavTargetId(attributes, tagName) {
|
|
|
5985
7407
|
}
|
|
5986
7408
|
function isAuthStorageKeyLiteral(node) {
|
|
5987
7409
|
if (!node) return false;
|
|
5988
|
-
if (
|
|
7410
|
+
if (ts3.isStringLiteral(node) || ts3.isNoSubstitutionTemplateLiteral(node)) {
|
|
5989
7411
|
if (node.text === "decantr_authenticated") return false;
|
|
5990
7412
|
return /(?:token|auth|jwt|session|access_token|refresh_token)/i.test(node.text);
|
|
5991
7413
|
}
|
|
5992
7414
|
return false;
|
|
5993
7415
|
}
|
|
5994
7416
|
function isBrowserStorageObject(node) {
|
|
5995
|
-
return
|
|
7417
|
+
return ts3.isIdentifier(node) && (node.text === "localStorage" || node.text === "sessionStorage");
|
|
5996
7418
|
}
|
|
5997
7419
|
function isDocumentObject(node) {
|
|
5998
|
-
return
|
|
7420
|
+
return ts3.isIdentifier(node) && node.text === "document";
|
|
5999
7421
|
}
|
|
6000
7422
|
function isCookiePropertyAccess(node) {
|
|
6001
|
-
return
|
|
7423
|
+
return ts3.isPropertyAccessExpression(node) && isDocumentObject(node.expression) && isPropertyNamed(node.name, "cookie");
|
|
6002
7424
|
}
|
|
6003
7425
|
function hasAuthCredentialText(node, sourceFile) {
|
|
6004
7426
|
return /(?:token|auth|jwt|session|access_token|refresh_token)/i.test(node.getText(sourceFile));
|
|
6005
7427
|
}
|
|
6006
7428
|
function isAuthorizationHeaderName(node) {
|
|
6007
7429
|
if (!node) return false;
|
|
6008
|
-
if (
|
|
7430
|
+
if (ts3.isIdentifier(node)) {
|
|
6009
7431
|
return /^authorization$/i.test(node.text);
|
|
6010
7432
|
}
|
|
6011
|
-
if (
|
|
7433
|
+
if (ts3.isStringLiteral(node) || ts3.isNoSubstitutionTemplateLiteral(node)) {
|
|
6012
7434
|
return /^authorization$/i.test(node.text);
|
|
6013
7435
|
}
|
|
6014
|
-
if (
|
|
7436
|
+
if (ts3.isComputedPropertyName(node)) {
|
|
6015
7437
|
return isAuthorizationHeaderName(node.expression);
|
|
6016
7438
|
}
|
|
6017
7439
|
return false;
|
|
6018
7440
|
}
|
|
6019
7441
|
function isAuthorizationHeaderAccess(node) {
|
|
6020
7442
|
if (!node) return false;
|
|
6021
|
-
return
|
|
7443
|
+
return ts3.isPropertyAccessExpression(node) && isAuthorizationHeaderName(node.name) || ts3.isElementAccessExpression(node) && isAuthorizationHeaderName(node.argumentExpression);
|
|
6022
7444
|
}
|
|
6023
7445
|
function isHeaderClearValue(node) {
|
|
6024
7446
|
if (!node) return false;
|
|
@@ -6026,7 +7448,7 @@ function isHeaderClearValue(node) {
|
|
|
6026
7448
|
if (literal !== null) {
|
|
6027
7449
|
return literal.trim().length === 0;
|
|
6028
7450
|
}
|
|
6029
|
-
return node.kind ===
|
|
7451
|
+
return node.kind === ts3.SyntaxKind.NullKeyword || node.kind === ts3.SyntaxKind.UndefinedKeyword || ts3.isIdentifier(node) && node.text === "undefined";
|
|
6030
7452
|
}
|
|
6031
7453
|
function countAuthGuardSignals(code) {
|
|
6032
7454
|
const patterns = [
|
|
@@ -6775,7 +8197,7 @@ function propertyPathLooksLikeOpenRedirectQueryContainerBase(propertyPath) {
|
|
|
6775
8197
|
}
|
|
6776
8198
|
function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFile, namedExpressions = /* @__PURE__ */ new Map(), namedPropertyAliases = /* @__PURE__ */ new Map(), seenIdentifiers = /* @__PURE__ */ new Set(), seenFunctions = /* @__PURE__ */ new Set()) {
|
|
6777
8199
|
if (!expression) return false;
|
|
6778
|
-
if (
|
|
8200
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
6779
8201
|
return expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
6780
8202
|
expression.expression,
|
|
6781
8203
|
sourceFile,
|
|
@@ -6802,7 +8224,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6802
8224
|
)
|
|
6803
8225
|
);
|
|
6804
8226
|
}
|
|
6805
|
-
if (
|
|
8227
|
+
if (ts3.isBinaryExpression(expression) && (expression.operatorToken.kind === ts3.SyntaxKind.QuestionQuestionToken || expression.operatorToken.kind === ts3.SyntaxKind.BarBarToken)) {
|
|
6806
8228
|
return expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
6807
8229
|
expression.left,
|
|
6808
8230
|
sourceFile,
|
|
@@ -6819,7 +8241,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6819
8241
|
seenFunctions
|
|
6820
8242
|
);
|
|
6821
8243
|
}
|
|
6822
|
-
if (
|
|
8244
|
+
if (ts3.isConditionalExpression(expression)) {
|
|
6823
8245
|
return expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
6824
8246
|
expression.whenTrue,
|
|
6825
8247
|
sourceFile,
|
|
@@ -6836,7 +8258,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6836
8258
|
seenFunctions
|
|
6837
8259
|
);
|
|
6838
8260
|
}
|
|
6839
|
-
if (
|
|
8261
|
+
if (ts3.isTemplateExpression(expression)) {
|
|
6840
8262
|
return expression.templateSpans.some(
|
|
6841
8263
|
(span) => expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
6842
8264
|
span.expression,
|
|
@@ -6848,7 +8270,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6848
8270
|
)
|
|
6849
8271
|
);
|
|
6850
8272
|
}
|
|
6851
|
-
if (
|
|
8273
|
+
if (ts3.isTaggedTemplateExpression(expression)) {
|
|
6852
8274
|
return expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
6853
8275
|
expression.template,
|
|
6854
8276
|
sourceFile,
|
|
@@ -6858,7 +8280,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6858
8280
|
seenFunctions
|
|
6859
8281
|
);
|
|
6860
8282
|
}
|
|
6861
|
-
if (
|
|
8283
|
+
if (ts3.isNewExpression(expression) && expression.arguments && expression.arguments.length > 0 && expressionLooksLikeBrowserGlobalBooleanHelper(
|
|
6862
8284
|
expression.expression,
|
|
6863
8285
|
sourceFile,
|
|
6864
8286
|
namedExpressions,
|
|
@@ -6874,7 +8296,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6874
8296
|
)) {
|
|
6875
8297
|
return true;
|
|
6876
8298
|
}
|
|
6877
|
-
if (
|
|
8299
|
+
if (ts3.isNewExpression(expression) && expression.arguments && expression.arguments.length > 0 && expressionLooksLikeBrowserGlobalNumberHelper(
|
|
6878
8300
|
expression.expression,
|
|
6879
8301
|
sourceFile,
|
|
6880
8302
|
namedExpressions,
|
|
@@ -6890,7 +8312,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6890
8312
|
)) {
|
|
6891
8313
|
return true;
|
|
6892
8314
|
}
|
|
6893
|
-
if (
|
|
8315
|
+
if (ts3.isNewExpression(expression) && expression.arguments && expression.arguments.length > 0 && expressionLooksLikeBrowserGlobalObjectHelper(
|
|
6894
8316
|
expression.expression,
|
|
6895
8317
|
sourceFile,
|
|
6896
8318
|
namedExpressions,
|
|
@@ -6906,7 +8328,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6906
8328
|
)) {
|
|
6907
8329
|
return true;
|
|
6908
8330
|
}
|
|
6909
|
-
if (
|
|
8331
|
+
if (ts3.isNewExpression(expression) && expression.arguments && expression.arguments.length > 0 && expressionLooksLikeBrowserGlobalStringHelper(
|
|
6910
8332
|
expression.expression,
|
|
6911
8333
|
sourceFile,
|
|
6912
8334
|
namedExpressions,
|
|
@@ -6938,7 +8360,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
6938
8360
|
)) {
|
|
6939
8361
|
return true;
|
|
6940
8362
|
}
|
|
6941
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8363
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalSymbolHelper(
|
|
6942
8364
|
expression.arguments[0],
|
|
6943
8365
|
sourceFile,
|
|
6944
8366
|
namedExpressions,
|
|
@@ -7002,7 +8424,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7002
8424
|
)) {
|
|
7003
8425
|
return true;
|
|
7004
8426
|
}
|
|
7005
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8427
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalBigIntHelper(
|
|
7006
8428
|
expression.arguments[0],
|
|
7007
8429
|
sourceFile,
|
|
7008
8430
|
namedExpressions,
|
|
@@ -7066,7 +8488,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7066
8488
|
)) {
|
|
7067
8489
|
return true;
|
|
7068
8490
|
}
|
|
7069
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8491
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalBooleanHelper(
|
|
7070
8492
|
expression.arguments[0],
|
|
7071
8493
|
sourceFile,
|
|
7072
8494
|
namedExpressions,
|
|
@@ -7130,7 +8552,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7130
8552
|
)) {
|
|
7131
8553
|
return true;
|
|
7132
8554
|
}
|
|
7133
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8555
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalNumberHelper(
|
|
7134
8556
|
expression.arguments[0],
|
|
7135
8557
|
sourceFile,
|
|
7136
8558
|
namedExpressions,
|
|
@@ -7194,7 +8616,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7194
8616
|
)) {
|
|
7195
8617
|
return true;
|
|
7196
8618
|
}
|
|
7197
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8619
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalObjectHelper(
|
|
7198
8620
|
expression.arguments[0],
|
|
7199
8621
|
sourceFile,
|
|
7200
8622
|
namedExpressions,
|
|
@@ -7242,7 +8664,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7242
8664
|
)) {
|
|
7243
8665
|
return true;
|
|
7244
8666
|
}
|
|
7245
|
-
if (isCallLikeExpression(expression) &&
|
|
8667
|
+
if (isCallLikeExpression(expression) && ts3.isIdentifier(expression.expression) && [
|
|
7246
8668
|
"String",
|
|
7247
8669
|
"atob",
|
|
7248
8670
|
"btoa",
|
|
@@ -7278,7 +8700,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7278
8700
|
)) {
|
|
7279
8701
|
return true;
|
|
7280
8702
|
}
|
|
7281
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8703
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeJsonStringifyHelper(
|
|
7282
8704
|
expression.arguments[0],
|
|
7283
8705
|
sourceFile,
|
|
7284
8706
|
namedExpressions,
|
|
@@ -7342,7 +8764,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7342
8764
|
)) {
|
|
7343
8765
|
return true;
|
|
7344
8766
|
}
|
|
7345
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8767
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeJsonParseHelper(
|
|
7346
8768
|
expression.arguments[0],
|
|
7347
8769
|
sourceFile,
|
|
7348
8770
|
namedExpressions,
|
|
@@ -7406,7 +8828,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7406
8828
|
)) {
|
|
7407
8829
|
return true;
|
|
7408
8830
|
}
|
|
7409
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8831
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeStructuredCloneHelper(
|
|
7410
8832
|
expression.arguments[0],
|
|
7411
8833
|
sourceFile,
|
|
7412
8834
|
namedExpressions,
|
|
@@ -7470,7 +8892,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7470
8892
|
)) {
|
|
7471
8893
|
return true;
|
|
7472
8894
|
}
|
|
7473
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8895
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalStringHelper(
|
|
7474
8896
|
expression.arguments[0],
|
|
7475
8897
|
sourceFile,
|
|
7476
8898
|
namedExpressions,
|
|
@@ -7550,7 +8972,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7550
8972
|
)) {
|
|
7551
8973
|
return true;
|
|
7552
8974
|
}
|
|
7553
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
8975
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalUriCodecHelper(
|
|
7554
8976
|
expression.arguments[0],
|
|
7555
8977
|
sourceFile,
|
|
7556
8978
|
namedExpressions,
|
|
@@ -7598,7 +9020,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7598
9020
|
)) {
|
|
7599
9021
|
return true;
|
|
7600
9022
|
}
|
|
7601
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
9023
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBrowserGlobalBase64Helper(
|
|
7602
9024
|
expression.arguments[0],
|
|
7603
9025
|
sourceFile,
|
|
7604
9026
|
namedExpressions,
|
|
@@ -7646,7 +9068,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7646
9068
|
)) {
|
|
7647
9069
|
return true;
|
|
7648
9070
|
}
|
|
7649
|
-
if (isCallLikeExpression(expression) && (
|
|
9071
|
+
if (isCallLikeExpression(expression) && (ts3.isIdentifier(expression.expression) || isMemberAccessExpression(expression.expression)) && expression.arguments.length > 0 && expressionLooksLikeBufferFromHelper(
|
|
7650
9072
|
expression.expression,
|
|
7651
9073
|
sourceFile,
|
|
7652
9074
|
namedExpressions,
|
|
@@ -7698,7 +9120,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7698
9120
|
)) {
|
|
7699
9121
|
return true;
|
|
7700
9122
|
}
|
|
7701
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
9123
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && expression.arguments.length > 2 && expressionLooksLikeBufferFromHelper(
|
|
7702
9124
|
expression.arguments[0],
|
|
7703
9125
|
sourceFile,
|
|
7704
9126
|
namedExpressions,
|
|
@@ -7746,7 +9168,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7746
9168
|
)) {
|
|
7747
9169
|
return true;
|
|
7748
9170
|
}
|
|
7749
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && isMemberAccessNamed(expression.expression, "from") &&
|
|
9171
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && isMemberAccessNamed(expression.expression, "from") && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Buffer" && expression.arguments.length > 0 && expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
7750
9172
|
expression.arguments[0],
|
|
7751
9173
|
sourceFile,
|
|
7752
9174
|
namedExpressions,
|
|
@@ -7925,7 +9347,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7925
9347
|
)) {
|
|
7926
9348
|
return true;
|
|
7927
9349
|
}
|
|
7928
|
-
if (isElementLikeAccessExpression(expression) &&
|
|
9350
|
+
if (isElementLikeAccessExpression(expression) && ts3.isNumericLiteral(expression.argumentExpression) && expression.argumentExpression.text === "1" && isMemberAccessExpression(expression.expression) && isMemberAccessNamed(expression.expression, "value") && isCallLikeExpression(expression.expression.expression) && isMemberAccessExpression(expression.expression.expression.expression) && isMemberAccessNamed(expression.expression.expression.expression, "next") && isCallLikeExpression(expression.expression.expression.expression.expression) && isMemberAccessExpression(expression.expression.expression.expression.expression.expression) && isMemberAccessNamed(
|
|
7929
9351
|
expression.expression.expression.expression.expression.expression,
|
|
7930
9352
|
"entries"
|
|
7931
9353
|
) && expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
@@ -7958,7 +9380,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7958
9380
|
)) {
|
|
7959
9381
|
return true;
|
|
7960
9382
|
}
|
|
7961
|
-
if (isCallLikeExpression(expression) && isPropertyLikeAccessExpression(expression.expression) &&
|
|
9383
|
+
if (isCallLikeExpression(expression) && isPropertyLikeAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Array" && isPropertyNamed(expression.expression.name, "from") && expression.arguments.length > 0 && expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
7962
9384
|
expression.arguments[0],
|
|
7963
9385
|
sourceFile,
|
|
7964
9386
|
namedExpressions,
|
|
@@ -7968,8 +9390,8 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
7968
9390
|
)) {
|
|
7969
9391
|
return true;
|
|
7970
9392
|
}
|
|
7971
|
-
if (
|
|
7972
|
-
(element) =>
|
|
9393
|
+
if (ts3.isArrayLiteralExpression(expression) && expression.elements.some(
|
|
9394
|
+
(element) => ts3.isSpreadElement(element) && expressionLooksLikeOpenRedirectQueryGetterFunction(
|
|
7973
9395
|
element.expression,
|
|
7974
9396
|
sourceFile,
|
|
7975
9397
|
namedExpressions,
|
|
@@ -8020,7 +9442,7 @@ function expressionLooksLikeOpenRedirectQueryGetterFunction(expression, sourceFi
|
|
|
8020
9442
|
)) {
|
|
8021
9443
|
return true;
|
|
8022
9444
|
}
|
|
8023
|
-
if (
|
|
9445
|
+
if (ts3.isIdentifier(expression)) {
|
|
8024
9446
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8025
9447
|
const initializer = namedExpressions.get(expression.text);
|
|
8026
9448
|
if (initializer) {
|
|
@@ -8054,7 +9476,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8054
9476
|
if (OPEN_REDIRECT_SOURCE_REGEX.test(expression.getText(sourceFile))) {
|
|
8055
9477
|
return true;
|
|
8056
9478
|
}
|
|
8057
|
-
if (
|
|
9479
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8058
9480
|
return expressionContainsOpenRedirectSource(
|
|
8059
9481
|
expression.expression,
|
|
8060
9482
|
sourceFile,
|
|
@@ -8064,7 +9486,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8064
9486
|
seenFunctions
|
|
8065
9487
|
);
|
|
8066
9488
|
}
|
|
8067
|
-
if (
|
|
9489
|
+
if (ts3.isBinaryExpression(expression)) {
|
|
8068
9490
|
return expressionContainsOpenRedirectSource(
|
|
8069
9491
|
expression.left,
|
|
8070
9492
|
sourceFile,
|
|
@@ -8081,7 +9503,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8081
9503
|
seenFunctions
|
|
8082
9504
|
);
|
|
8083
9505
|
}
|
|
8084
|
-
if (
|
|
9506
|
+
if (ts3.isConditionalExpression(expression)) {
|
|
8085
9507
|
return expressionContainsOpenRedirectSource(
|
|
8086
9508
|
expression.condition,
|
|
8087
9509
|
sourceFile,
|
|
@@ -8105,7 +9527,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8105
9527
|
seenFunctions
|
|
8106
9528
|
);
|
|
8107
9529
|
}
|
|
8108
|
-
if (
|
|
9530
|
+
if (ts3.isTemplateExpression(expression)) {
|
|
8109
9531
|
return expression.templateSpans.some(
|
|
8110
9532
|
(span) => expressionContainsOpenRedirectSource(
|
|
8111
9533
|
span.expression,
|
|
@@ -8117,7 +9539,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8117
9539
|
)
|
|
8118
9540
|
);
|
|
8119
9541
|
}
|
|
8120
|
-
if (
|
|
9542
|
+
if (ts3.isTaggedTemplateExpression(expression)) {
|
|
8121
9543
|
return expressionContainsOpenRedirectSource(
|
|
8122
9544
|
expression.template,
|
|
8123
9545
|
sourceFile,
|
|
@@ -8127,7 +9549,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8127
9549
|
seenFunctions
|
|
8128
9550
|
);
|
|
8129
9551
|
}
|
|
8130
|
-
if (
|
|
9552
|
+
if (ts3.isNewExpression(expression)) {
|
|
8131
9553
|
return (expression.arguments ?? []).some(
|
|
8132
9554
|
(argument) => expressionContainsOpenRedirectSource(
|
|
8133
9555
|
argument,
|
|
@@ -8139,9 +9561,9 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8139
9561
|
)
|
|
8140
9562
|
);
|
|
8141
9563
|
}
|
|
8142
|
-
if (
|
|
9564
|
+
if (ts3.isArrayLiteralExpression(expression)) {
|
|
8143
9565
|
return expression.elements.some((element) => {
|
|
8144
|
-
if (
|
|
9566
|
+
if (ts3.isSpreadElement(element)) {
|
|
8145
9567
|
return expressionContainsOpenRedirectSource(
|
|
8146
9568
|
element.expression,
|
|
8147
9569
|
sourceFile,
|
|
@@ -8151,7 +9573,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8151
9573
|
seenFunctions
|
|
8152
9574
|
);
|
|
8153
9575
|
}
|
|
8154
|
-
return
|
|
9576
|
+
return ts3.isExpression(element) && expressionContainsOpenRedirectSource(
|
|
8155
9577
|
element,
|
|
8156
9578
|
sourceFile,
|
|
8157
9579
|
namedExpressions,
|
|
@@ -8161,9 +9583,9 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8161
9583
|
);
|
|
8162
9584
|
});
|
|
8163
9585
|
}
|
|
8164
|
-
if (
|
|
9586
|
+
if (ts3.isObjectLiteralExpression(expression)) {
|
|
8165
9587
|
return expression.properties.some((property) => {
|
|
8166
|
-
if (
|
|
9588
|
+
if (ts3.isPropertyAssignment(property)) {
|
|
8167
9589
|
return expressionContainsOpenRedirectSource(
|
|
8168
9590
|
property.initializer,
|
|
8169
9591
|
sourceFile,
|
|
@@ -8173,7 +9595,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8173
9595
|
seenFunctions
|
|
8174
9596
|
);
|
|
8175
9597
|
}
|
|
8176
|
-
if (
|
|
9598
|
+
if (ts3.isShorthandPropertyAssignment(property)) {
|
|
8177
9599
|
return expressionContainsOpenRedirectSource(
|
|
8178
9600
|
property.name,
|
|
8179
9601
|
sourceFile,
|
|
@@ -8183,7 +9605,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8183
9605
|
seenFunctions
|
|
8184
9606
|
);
|
|
8185
9607
|
}
|
|
8186
|
-
if (
|
|
9608
|
+
if (ts3.isSpreadAssignment(property)) {
|
|
8187
9609
|
return expressionContainsOpenRedirectSource(
|
|
8188
9610
|
property.expression,
|
|
8189
9611
|
sourceFile,
|
|
@@ -8238,7 +9660,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8238
9660
|
}
|
|
8239
9661
|
}
|
|
8240
9662
|
}
|
|
8241
|
-
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) &&
|
|
9663
|
+
if (isCallLikeExpression(expression) && isMemberAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Reflect" && isMemberAccessNamed(expression.expression, "apply") && isOpenRedirectQueryKeyExpression(
|
|
8242
9664
|
getAliasedApplyArgumentExpression(expression.arguments[2], 0, namedExpressions, /* @__PURE__ */ new Set()),
|
|
8243
9665
|
namedExpressions,
|
|
8244
9666
|
seenIdentifiers
|
|
@@ -8354,7 +9776,7 @@ function expressionContainsOpenRedirectSource(expression, sourceFile, namedExpre
|
|
|
8354
9776
|
)) {
|
|
8355
9777
|
return true;
|
|
8356
9778
|
}
|
|
8357
|
-
if (
|
|
9779
|
+
if (ts3.isIdentifier(expression)) {
|
|
8358
9780
|
if (seenIdentifiers.has(expression.text)) {
|
|
8359
9781
|
return false;
|
|
8360
9782
|
}
|
|
@@ -8392,14 +9814,14 @@ function isOpenRedirectQueryKeyExpression(expression, namedExpressions = /* @__P
|
|
|
8392
9814
|
return true;
|
|
8393
9815
|
}
|
|
8394
9816
|
if (!expression) return false;
|
|
8395
|
-
if (
|
|
9817
|
+
if (ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression) || ts3.isParenthesizedExpression(expression)) {
|
|
8396
9818
|
return isOpenRedirectQueryKeyExpression(
|
|
8397
9819
|
expression.expression,
|
|
8398
9820
|
namedExpressions,
|
|
8399
9821
|
seenIdentifiers
|
|
8400
9822
|
);
|
|
8401
9823
|
}
|
|
8402
|
-
if (
|
|
9824
|
+
if (ts3.isIdentifier(expression)) {
|
|
8403
9825
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8404
9826
|
const initializer = namedExpressions.get(expression.text);
|
|
8405
9827
|
if (!initializer) return false;
|
|
@@ -8415,7 +9837,7 @@ function expressionLooksLikeOpenRedirectQueryCarrier(expression, sourceFile, nam
|
|
|
8415
9837
|
if (OPEN_REDIRECT_QUERY_CARRIER_REGEX.test(expression.getText(sourceFile))) {
|
|
8416
9838
|
return true;
|
|
8417
9839
|
}
|
|
8418
|
-
if (
|
|
9840
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8419
9841
|
return expressionLooksLikeOpenRedirectQueryCarrier(
|
|
8420
9842
|
expression.expression,
|
|
8421
9843
|
sourceFile,
|
|
@@ -8424,7 +9846,7 @@ function expressionLooksLikeOpenRedirectQueryCarrier(expression, sourceFile, nam
|
|
|
8424
9846
|
seenIdentifiers
|
|
8425
9847
|
);
|
|
8426
9848
|
}
|
|
8427
|
-
if (isCallLikeExpression(expression) && isPropertyLikeAccessExpression(expression.expression) &&
|
|
9849
|
+
if (isCallLikeExpression(expression) && isPropertyLikeAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Object" && isPropertyNamed(expression.expression.name, "fromEntries") && expression.arguments.length > 0) {
|
|
8428
9850
|
const entriesExpression = expression.arguments[0];
|
|
8429
9851
|
if (expressionLooksLikeOpenRedirectSearchParamsCarrier(
|
|
8430
9852
|
entriesExpression,
|
|
@@ -8454,7 +9876,7 @@ function expressionLooksLikeOpenRedirectQueryCarrier(expression, sourceFile, nam
|
|
|
8454
9876
|
seenIdentifiers
|
|
8455
9877
|
);
|
|
8456
9878
|
}
|
|
8457
|
-
if (
|
|
9879
|
+
if (ts3.isIdentifier(expression)) {
|
|
8458
9880
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8459
9881
|
const initializer = namedExpressions.get(expression.text);
|
|
8460
9882
|
if (initializer) {
|
|
@@ -8487,7 +9909,7 @@ function expressionLooksLikeOpenRedirectQueryCarrier(expression, sourceFile, nam
|
|
|
8487
9909
|
}
|
|
8488
9910
|
function expressionLooksLikeOpenRedirectEntriesCarrier(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
8489
9911
|
if (!expression) return false;
|
|
8490
|
-
if (
|
|
9912
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8491
9913
|
return expressionLooksLikeOpenRedirectEntriesCarrier(
|
|
8492
9914
|
expression.expression,
|
|
8493
9915
|
sourceFile,
|
|
@@ -8496,7 +9918,7 @@ function expressionLooksLikeOpenRedirectEntriesCarrier(expression, sourceFile, n
|
|
|
8496
9918
|
seenIdentifiers
|
|
8497
9919
|
);
|
|
8498
9920
|
}
|
|
8499
|
-
if (isCallLikeExpression(expression) && isPropertyLikeAccessExpression(expression.expression) &&
|
|
9921
|
+
if (isCallLikeExpression(expression) && isPropertyLikeAccessExpression(expression.expression) && ts3.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "Array" && isPropertyNamed(expression.expression.name, "from") && expression.arguments.length > 0 && (expressionLooksLikeOpenRedirectEntriesCarrier(
|
|
8500
9922
|
expression.arguments[0],
|
|
8501
9923
|
sourceFile,
|
|
8502
9924
|
namedExpressions,
|
|
@@ -8511,8 +9933,8 @@ function expressionLooksLikeOpenRedirectEntriesCarrier(expression, sourceFile, n
|
|
|
8511
9933
|
))) {
|
|
8512
9934
|
return true;
|
|
8513
9935
|
}
|
|
8514
|
-
if (
|
|
8515
|
-
(element) =>
|
|
9936
|
+
if (ts3.isArrayLiteralExpression(expression) && expression.elements.some(
|
|
9937
|
+
(element) => ts3.isSpreadElement(element) && (expressionLooksLikeOpenRedirectEntriesCarrier(
|
|
8516
9938
|
element.expression,
|
|
8517
9939
|
sourceFile,
|
|
8518
9940
|
namedExpressions,
|
|
@@ -8537,7 +9959,7 @@ function expressionLooksLikeOpenRedirectEntriesCarrier(expression, sourceFile, n
|
|
|
8537
9959
|
)) {
|
|
8538
9960
|
return true;
|
|
8539
9961
|
}
|
|
8540
|
-
if (
|
|
9962
|
+
if (ts3.isIdentifier(expression)) {
|
|
8541
9963
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8542
9964
|
const initializer = namedExpressions.get(expression.text);
|
|
8543
9965
|
if (!initializer) return false;
|
|
@@ -8559,7 +9981,7 @@ function expressionLooksLikeOpenRedirectQueryContainerBase(expression, sourceFil
|
|
|
8559
9981
|
if (OPEN_REDIRECT_QUERY_CONTAINER_BASE_REGEX.test(expression.getText(sourceFile))) {
|
|
8560
9982
|
return true;
|
|
8561
9983
|
}
|
|
8562
|
-
if (
|
|
9984
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8563
9985
|
return expressionLooksLikeOpenRedirectQueryContainerBase(
|
|
8564
9986
|
expression.expression,
|
|
8565
9987
|
sourceFile,
|
|
@@ -8568,10 +9990,10 @@ function expressionLooksLikeOpenRedirectQueryContainerBase(expression, sourceFil
|
|
|
8568
9990
|
seenIdentifiers
|
|
8569
9991
|
);
|
|
8570
9992
|
}
|
|
8571
|
-
if (isCallLikeExpression(expression) && (
|
|
9993
|
+
if (isCallLikeExpression(expression) && (ts3.isIdentifier(expression.expression) && expression.expression.text === "useRouter" || isMemberAccessExpression(expression.expression) && isMemberAccessNamed(expression.expression, "useRouter"))) {
|
|
8572
9994
|
return true;
|
|
8573
9995
|
}
|
|
8574
|
-
if (
|
|
9996
|
+
if (ts3.isIdentifier(expression)) {
|
|
8575
9997
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8576
9998
|
const initializer = namedExpressions.get(expression.text);
|
|
8577
9999
|
if (initializer) {
|
|
@@ -8609,7 +10031,7 @@ function expressionLooksLikeLocationQuerySource(expression, sourceFile, namedExp
|
|
|
8609
10031
|
if (LOCATION_QUERY_SOURCE_REGEX.test(expression.getText(sourceFile))) {
|
|
8610
10032
|
return true;
|
|
8611
10033
|
}
|
|
8612
|
-
if (
|
|
10034
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8613
10035
|
return expressionLooksLikeLocationQuerySource(
|
|
8614
10036
|
expression.expression,
|
|
8615
10037
|
sourceFile,
|
|
@@ -8642,7 +10064,7 @@ function expressionLooksLikeLocationQuerySource(expression, sourceFile, namedExp
|
|
|
8642
10064
|
))) {
|
|
8643
10065
|
return true;
|
|
8644
10066
|
}
|
|
8645
|
-
if (
|
|
10067
|
+
if (ts3.isIdentifier(expression)) {
|
|
8646
10068
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8647
10069
|
const initializer = namedExpressions.get(expression.text);
|
|
8648
10070
|
if (initializer) {
|
|
@@ -8678,10 +10100,10 @@ function expressionLooksLikeLocationQuerySource(expression, sourceFile, namedExp
|
|
|
8678
10100
|
}
|
|
8679
10101
|
function expressionLooksLikeWindowObjectSource(expression, sourceFile, namedExpressions, seenIdentifiers) {
|
|
8680
10102
|
if (!expression) return false;
|
|
8681
|
-
if (
|
|
10103
|
+
if (ts3.isIdentifier(expression) && ["window", "globalThis", "document", "self", "parent", "top"].includes(expression.text)) {
|
|
8682
10104
|
return true;
|
|
8683
10105
|
}
|
|
8684
|
-
if (
|
|
10106
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8685
10107
|
return expressionLooksLikeWindowObjectSource(
|
|
8686
10108
|
expression.expression,
|
|
8687
10109
|
sourceFile,
|
|
@@ -8689,7 +10111,7 @@ function expressionLooksLikeWindowObjectSource(expression, sourceFile, namedExpr
|
|
|
8689
10111
|
seenIdentifiers
|
|
8690
10112
|
);
|
|
8691
10113
|
}
|
|
8692
|
-
if (
|
|
10114
|
+
if (ts3.isIdentifier(expression)) {
|
|
8693
10115
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8694
10116
|
const initializer = namedExpressions.get(expression.text);
|
|
8695
10117
|
if (!initializer) return false;
|
|
@@ -8710,7 +10132,7 @@ function expressionLooksLikeLocationObjectSource(expression, sourceFile, namedEx
|
|
|
8710
10132
|
if (isLocationObjectExpression(expression)) {
|
|
8711
10133
|
return true;
|
|
8712
10134
|
}
|
|
8713
|
-
if (
|
|
10135
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8714
10136
|
return expressionLooksLikeLocationObjectSource(
|
|
8715
10137
|
expression.expression,
|
|
8716
10138
|
sourceFile,
|
|
@@ -8719,7 +10141,7 @@ function expressionLooksLikeLocationObjectSource(expression, sourceFile, namedEx
|
|
|
8719
10141
|
seenIdentifiers
|
|
8720
10142
|
);
|
|
8721
10143
|
}
|
|
8722
|
-
if (isCallLikeExpression(expression) && (
|
|
10144
|
+
if (isCallLikeExpression(expression) && (ts3.isIdentifier(expression.expression) && expression.expression.text === "useLocation" || isMemberAccessExpression(expression.expression) && isMemberAccessNamed(expression.expression, "useLocation"))) {
|
|
8723
10145
|
return true;
|
|
8724
10146
|
}
|
|
8725
10147
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "location") && expressionLooksLikeWindowObjectSource(
|
|
@@ -8730,7 +10152,7 @@ function expressionLooksLikeLocationObjectSource(expression, sourceFile, namedEx
|
|
|
8730
10152
|
)) {
|
|
8731
10153
|
return true;
|
|
8732
10154
|
}
|
|
8733
|
-
if (
|
|
10155
|
+
if (ts3.isIdentifier(expression)) {
|
|
8734
10156
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8735
10157
|
const initializer = namedExpressions.get(expression.text);
|
|
8736
10158
|
if (initializer) {
|
|
@@ -8759,10 +10181,10 @@ function expressionLooksLikeLocationObjectSource(expression, sourceFile, namedEx
|
|
|
8759
10181
|
}
|
|
8760
10182
|
function expressionLooksLikeHistoryObjectSource(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
8761
10183
|
if (!expression) return false;
|
|
8762
|
-
if (
|
|
10184
|
+
if (ts3.isIdentifier(expression) && expression.text === "history") {
|
|
8763
10185
|
return true;
|
|
8764
10186
|
}
|
|
8765
|
-
if (
|
|
10187
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8766
10188
|
return expressionLooksLikeHistoryObjectSource(
|
|
8767
10189
|
expression.expression,
|
|
8768
10190
|
sourceFile,
|
|
@@ -8779,7 +10201,7 @@ function expressionLooksLikeHistoryObjectSource(expression, sourceFile, namedExp
|
|
|
8779
10201
|
)) {
|
|
8780
10202
|
return true;
|
|
8781
10203
|
}
|
|
8782
|
-
if (
|
|
10204
|
+
if (ts3.isIdentifier(expression)) {
|
|
8783
10205
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8784
10206
|
const initializer = namedExpressions.get(expression.text);
|
|
8785
10207
|
if (initializer) {
|
|
@@ -8808,10 +10230,10 @@ function expressionLooksLikeHistoryObjectSource(expression, sourceFile, namedExp
|
|
|
8808
10230
|
}
|
|
8809
10231
|
function expressionLooksLikeBufferObjectSource(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
8810
10232
|
if (!expression) return false;
|
|
8811
|
-
if (
|
|
10233
|
+
if (ts3.isIdentifier(expression) && expression.text === "Buffer") {
|
|
8812
10234
|
return true;
|
|
8813
10235
|
}
|
|
8814
|
-
if (
|
|
10236
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8815
10237
|
return expressionLooksLikeBufferObjectSource(
|
|
8816
10238
|
expression.expression,
|
|
8817
10239
|
sourceFile,
|
|
@@ -8828,7 +10250,7 @@ function expressionLooksLikeBufferObjectSource(expression, sourceFile, namedExpr
|
|
|
8828
10250
|
)) {
|
|
8829
10251
|
return true;
|
|
8830
10252
|
}
|
|
8831
|
-
if (
|
|
10253
|
+
if (ts3.isIdentifier(expression)) {
|
|
8832
10254
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8833
10255
|
const initializer = namedExpressions.get(expression.text);
|
|
8834
10256
|
if (initializer) {
|
|
@@ -8875,7 +10297,7 @@ function expressionLooksLikeBufferFromHelper(expression, sourceFile, namedExpres
|
|
|
8875
10297
|
)) {
|
|
8876
10298
|
return true;
|
|
8877
10299
|
}
|
|
8878
|
-
if (
|
|
10300
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8879
10301
|
return expressionLooksLikeBufferFromHelper(
|
|
8880
10302
|
expression.expression,
|
|
8881
10303
|
sourceFile,
|
|
@@ -8884,7 +10306,7 @@ function expressionLooksLikeBufferFromHelper(expression, sourceFile, namedExpres
|
|
|
8884
10306
|
seenIdentifiers
|
|
8885
10307
|
);
|
|
8886
10308
|
}
|
|
8887
|
-
if (
|
|
10309
|
+
if (ts3.isIdentifier(expression)) {
|
|
8888
10310
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8889
10311
|
const initializer = namedExpressions.get(expression.text);
|
|
8890
10312
|
if (initializer) {
|
|
@@ -8914,10 +10336,10 @@ function expressionLooksLikeBufferFromHelper(expression, sourceFile, namedExpres
|
|
|
8914
10336
|
}
|
|
8915
10337
|
function expressionLooksLikeJsonObjectSource(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
8916
10338
|
if (!expression) return false;
|
|
8917
|
-
if (
|
|
10339
|
+
if (ts3.isIdentifier(expression) && expression.text === "JSON") {
|
|
8918
10340
|
return true;
|
|
8919
10341
|
}
|
|
8920
|
-
if (
|
|
10342
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8921
10343
|
return expressionLooksLikeJsonObjectSource(
|
|
8922
10344
|
expression.expression,
|
|
8923
10345
|
sourceFile,
|
|
@@ -8934,7 +10356,7 @@ function expressionLooksLikeJsonObjectSource(expression, sourceFile, namedExpres
|
|
|
8934
10356
|
)) {
|
|
8935
10357
|
return true;
|
|
8936
10358
|
}
|
|
8937
|
-
if (
|
|
10359
|
+
if (ts3.isIdentifier(expression)) {
|
|
8938
10360
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8939
10361
|
const initializer = namedExpressions.get(expression.text);
|
|
8940
10362
|
if (initializer) {
|
|
@@ -8981,7 +10403,7 @@ function expressionLooksLikeJsonStringifyHelper(expression, sourceFile, namedExp
|
|
|
8981
10403
|
)) {
|
|
8982
10404
|
return true;
|
|
8983
10405
|
}
|
|
8984
|
-
if (
|
|
10406
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
8985
10407
|
return expressionLooksLikeJsonStringifyHelper(
|
|
8986
10408
|
expression.expression,
|
|
8987
10409
|
sourceFile,
|
|
@@ -8990,7 +10412,7 @@ function expressionLooksLikeJsonStringifyHelper(expression, sourceFile, namedExp
|
|
|
8990
10412
|
seenIdentifiers
|
|
8991
10413
|
);
|
|
8992
10414
|
}
|
|
8993
|
-
if (
|
|
10415
|
+
if (ts3.isIdentifier(expression)) {
|
|
8994
10416
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
8995
10417
|
const initializer = namedExpressions.get(expression.text);
|
|
8996
10418
|
if (initializer) {
|
|
@@ -9038,7 +10460,7 @@ function expressionLooksLikeJsonParseHelper(expression, sourceFile, namedExpress
|
|
|
9038
10460
|
)) {
|
|
9039
10461
|
return true;
|
|
9040
10462
|
}
|
|
9041
|
-
if (
|
|
10463
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9042
10464
|
return expressionLooksLikeJsonParseHelper(
|
|
9043
10465
|
expression.expression,
|
|
9044
10466
|
sourceFile,
|
|
@@ -9047,7 +10469,7 @@ function expressionLooksLikeJsonParseHelper(expression, sourceFile, namedExpress
|
|
|
9047
10469
|
seenIdentifiers
|
|
9048
10470
|
);
|
|
9049
10471
|
}
|
|
9050
|
-
if (
|
|
10472
|
+
if (ts3.isIdentifier(expression)) {
|
|
9051
10473
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9052
10474
|
const initializer = namedExpressions.get(expression.text);
|
|
9053
10475
|
if (initializer) {
|
|
@@ -9077,7 +10499,7 @@ function expressionLooksLikeJsonParseHelper(expression, sourceFile, namedExpress
|
|
|
9077
10499
|
}
|
|
9078
10500
|
function expressionLooksLikeStructuredCloneHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9079
10501
|
if (!expression) return false;
|
|
9080
|
-
if (
|
|
10502
|
+
if (ts3.isIdentifier(expression) && expression.text === "structuredClone") {
|
|
9081
10503
|
return true;
|
|
9082
10504
|
}
|
|
9083
10505
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "structuredClone") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9097,7 +10519,7 @@ function expressionLooksLikeStructuredCloneHelper(expression, sourceFile, namedE
|
|
|
9097
10519
|
)) {
|
|
9098
10520
|
return true;
|
|
9099
10521
|
}
|
|
9100
|
-
if (
|
|
10522
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9101
10523
|
return expressionLooksLikeStructuredCloneHelper(
|
|
9102
10524
|
expression.expression,
|
|
9103
10525
|
sourceFile,
|
|
@@ -9106,7 +10528,7 @@ function expressionLooksLikeStructuredCloneHelper(expression, sourceFile, namedE
|
|
|
9106
10528
|
seenIdentifiers
|
|
9107
10529
|
);
|
|
9108
10530
|
}
|
|
9109
|
-
if (
|
|
10531
|
+
if (ts3.isIdentifier(expression)) {
|
|
9110
10532
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9111
10533
|
const initializer = namedExpressions.get(expression.text);
|
|
9112
10534
|
if (initializer) {
|
|
@@ -9135,7 +10557,7 @@ function expressionLooksLikeStructuredCloneHelper(expression, sourceFile, namedE
|
|
|
9135
10557
|
}
|
|
9136
10558
|
function expressionLooksLikeBrowserGlobalSymbolHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9137
10559
|
if (!expression) return false;
|
|
9138
|
-
if (
|
|
10560
|
+
if (ts3.isIdentifier(expression) && expression.text === "Symbol") {
|
|
9139
10561
|
return true;
|
|
9140
10562
|
}
|
|
9141
10563
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "Symbol") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9155,7 +10577,7 @@ function expressionLooksLikeBrowserGlobalSymbolHelper(expression, sourceFile, na
|
|
|
9155
10577
|
)) {
|
|
9156
10578
|
return true;
|
|
9157
10579
|
}
|
|
9158
|
-
if (
|
|
10580
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9159
10581
|
return expressionLooksLikeBrowserGlobalSymbolHelper(
|
|
9160
10582
|
expression.expression,
|
|
9161
10583
|
sourceFile,
|
|
@@ -9164,7 +10586,7 @@ function expressionLooksLikeBrowserGlobalSymbolHelper(expression, sourceFile, na
|
|
|
9164
10586
|
seenIdentifiers
|
|
9165
10587
|
);
|
|
9166
10588
|
}
|
|
9167
|
-
if (
|
|
10589
|
+
if (ts3.isIdentifier(expression)) {
|
|
9168
10590
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9169
10591
|
const initializer = namedExpressions.get(expression.text);
|
|
9170
10592
|
if (initializer) {
|
|
@@ -9193,7 +10615,7 @@ function expressionLooksLikeBrowserGlobalSymbolHelper(expression, sourceFile, na
|
|
|
9193
10615
|
}
|
|
9194
10616
|
function expressionLooksLikeBrowserGlobalBigIntHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9195
10617
|
if (!expression) return false;
|
|
9196
|
-
if (
|
|
10618
|
+
if (ts3.isIdentifier(expression) && expression.text === "BigInt") {
|
|
9197
10619
|
return true;
|
|
9198
10620
|
}
|
|
9199
10621
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "BigInt") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9213,7 +10635,7 @@ function expressionLooksLikeBrowserGlobalBigIntHelper(expression, sourceFile, na
|
|
|
9213
10635
|
)) {
|
|
9214
10636
|
return true;
|
|
9215
10637
|
}
|
|
9216
|
-
if (
|
|
10638
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9217
10639
|
return expressionLooksLikeBrowserGlobalBigIntHelper(
|
|
9218
10640
|
expression.expression,
|
|
9219
10641
|
sourceFile,
|
|
@@ -9222,7 +10644,7 @@ function expressionLooksLikeBrowserGlobalBigIntHelper(expression, sourceFile, na
|
|
|
9222
10644
|
seenIdentifiers
|
|
9223
10645
|
);
|
|
9224
10646
|
}
|
|
9225
|
-
if (
|
|
10647
|
+
if (ts3.isIdentifier(expression)) {
|
|
9226
10648
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9227
10649
|
const initializer = namedExpressions.get(expression.text);
|
|
9228
10650
|
if (initializer) {
|
|
@@ -9251,7 +10673,7 @@ function expressionLooksLikeBrowserGlobalBigIntHelper(expression, sourceFile, na
|
|
|
9251
10673
|
}
|
|
9252
10674
|
function expressionLooksLikeBrowserGlobalBooleanHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9253
10675
|
if (!expression) return false;
|
|
9254
|
-
if (
|
|
10676
|
+
if (ts3.isIdentifier(expression) && expression.text === "Boolean") {
|
|
9255
10677
|
return true;
|
|
9256
10678
|
}
|
|
9257
10679
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "Boolean") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9271,7 +10693,7 @@ function expressionLooksLikeBrowserGlobalBooleanHelper(expression, sourceFile, n
|
|
|
9271
10693
|
)) {
|
|
9272
10694
|
return true;
|
|
9273
10695
|
}
|
|
9274
|
-
if (
|
|
10696
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9275
10697
|
return expressionLooksLikeBrowserGlobalBooleanHelper(
|
|
9276
10698
|
expression.expression,
|
|
9277
10699
|
sourceFile,
|
|
@@ -9280,7 +10702,7 @@ function expressionLooksLikeBrowserGlobalBooleanHelper(expression, sourceFile, n
|
|
|
9280
10702
|
seenIdentifiers
|
|
9281
10703
|
);
|
|
9282
10704
|
}
|
|
9283
|
-
if (
|
|
10705
|
+
if (ts3.isIdentifier(expression)) {
|
|
9284
10706
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9285
10707
|
const initializer = namedExpressions.get(expression.text);
|
|
9286
10708
|
if (initializer) {
|
|
@@ -9309,7 +10731,7 @@ function expressionLooksLikeBrowserGlobalBooleanHelper(expression, sourceFile, n
|
|
|
9309
10731
|
}
|
|
9310
10732
|
function expressionLooksLikeBrowserGlobalNumberHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9311
10733
|
if (!expression) return false;
|
|
9312
|
-
if (
|
|
10734
|
+
if (ts3.isIdentifier(expression) && expression.text === "Number") {
|
|
9313
10735
|
return true;
|
|
9314
10736
|
}
|
|
9315
10737
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "Number") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9329,7 +10751,7 @@ function expressionLooksLikeBrowserGlobalNumberHelper(expression, sourceFile, na
|
|
|
9329
10751
|
)) {
|
|
9330
10752
|
return true;
|
|
9331
10753
|
}
|
|
9332
|
-
if (
|
|
10754
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9333
10755
|
return expressionLooksLikeBrowserGlobalNumberHelper(
|
|
9334
10756
|
expression.expression,
|
|
9335
10757
|
sourceFile,
|
|
@@ -9338,7 +10760,7 @@ function expressionLooksLikeBrowserGlobalNumberHelper(expression, sourceFile, na
|
|
|
9338
10760
|
seenIdentifiers
|
|
9339
10761
|
);
|
|
9340
10762
|
}
|
|
9341
|
-
if (
|
|
10763
|
+
if (ts3.isIdentifier(expression)) {
|
|
9342
10764
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9343
10765
|
const initializer = namedExpressions.get(expression.text);
|
|
9344
10766
|
if (initializer) {
|
|
@@ -9367,7 +10789,7 @@ function expressionLooksLikeBrowserGlobalNumberHelper(expression, sourceFile, na
|
|
|
9367
10789
|
}
|
|
9368
10790
|
function expressionLooksLikeBrowserGlobalObjectHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9369
10791
|
if (!expression) return false;
|
|
9370
|
-
if (
|
|
10792
|
+
if (ts3.isIdentifier(expression) && expression.text === "Object") {
|
|
9371
10793
|
return true;
|
|
9372
10794
|
}
|
|
9373
10795
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "Object") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9387,7 +10809,7 @@ function expressionLooksLikeBrowserGlobalObjectHelper(expression, sourceFile, na
|
|
|
9387
10809
|
)) {
|
|
9388
10810
|
return true;
|
|
9389
10811
|
}
|
|
9390
|
-
if (
|
|
10812
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9391
10813
|
return expressionLooksLikeBrowserGlobalObjectHelper(
|
|
9392
10814
|
expression.expression,
|
|
9393
10815
|
sourceFile,
|
|
@@ -9396,7 +10818,7 @@ function expressionLooksLikeBrowserGlobalObjectHelper(expression, sourceFile, na
|
|
|
9396
10818
|
seenIdentifiers
|
|
9397
10819
|
);
|
|
9398
10820
|
}
|
|
9399
|
-
if (
|
|
10821
|
+
if (ts3.isIdentifier(expression)) {
|
|
9400
10822
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9401
10823
|
const initializer = namedExpressions.get(expression.text);
|
|
9402
10824
|
if (initializer) {
|
|
@@ -9425,7 +10847,7 @@ function expressionLooksLikeBrowserGlobalObjectHelper(expression, sourceFile, na
|
|
|
9425
10847
|
}
|
|
9426
10848
|
function expressionLooksLikeBrowserGlobalStringHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9427
10849
|
if (!expression) return false;
|
|
9428
|
-
if (
|
|
10850
|
+
if (ts3.isIdentifier(expression) && expression.text === "String") {
|
|
9429
10851
|
return true;
|
|
9430
10852
|
}
|
|
9431
10853
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "String") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9445,7 +10867,7 @@ function expressionLooksLikeBrowserGlobalStringHelper(expression, sourceFile, na
|
|
|
9445
10867
|
)) {
|
|
9446
10868
|
return true;
|
|
9447
10869
|
}
|
|
9448
|
-
if (
|
|
10870
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9449
10871
|
return expressionLooksLikeBrowserGlobalStringHelper(
|
|
9450
10872
|
expression.expression,
|
|
9451
10873
|
sourceFile,
|
|
@@ -9454,7 +10876,7 @@ function expressionLooksLikeBrowserGlobalStringHelper(expression, sourceFile, na
|
|
|
9454
10876
|
seenIdentifiers
|
|
9455
10877
|
);
|
|
9456
10878
|
}
|
|
9457
|
-
if (
|
|
10879
|
+
if (ts3.isIdentifier(expression)) {
|
|
9458
10880
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9459
10881
|
const initializer = namedExpressions.get(expression.text);
|
|
9460
10882
|
if (initializer) {
|
|
@@ -9483,7 +10905,7 @@ function expressionLooksLikeBrowserGlobalStringHelper(expression, sourceFile, na
|
|
|
9483
10905
|
}
|
|
9484
10906
|
function expressionLooksLikeBrowserGlobalBase64Helper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9485
10907
|
if (!expression) return false;
|
|
9486
|
-
if (
|
|
10908
|
+
if (ts3.isIdentifier(expression) && ["atob", "btoa"].includes(expression.text)) {
|
|
9487
10909
|
return true;
|
|
9488
10910
|
}
|
|
9489
10911
|
if (isMemberAccessExpression(expression) && isMemberAccessNamed(expression, "atob", "btoa") && expressionLooksLikeWindowObjectSource(
|
|
@@ -9503,7 +10925,7 @@ function expressionLooksLikeBrowserGlobalBase64Helper(expression, sourceFile, na
|
|
|
9503
10925
|
)) {
|
|
9504
10926
|
return true;
|
|
9505
10927
|
}
|
|
9506
|
-
if (
|
|
10928
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9507
10929
|
return expressionLooksLikeBrowserGlobalBase64Helper(
|
|
9508
10930
|
expression.expression,
|
|
9509
10931
|
sourceFile,
|
|
@@ -9512,7 +10934,7 @@ function expressionLooksLikeBrowserGlobalBase64Helper(expression, sourceFile, na
|
|
|
9512
10934
|
seenIdentifiers
|
|
9513
10935
|
);
|
|
9514
10936
|
}
|
|
9515
|
-
if (
|
|
10937
|
+
if (ts3.isIdentifier(expression)) {
|
|
9516
10938
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9517
10939
|
const initializer = namedExpressions.get(expression.text);
|
|
9518
10940
|
if (initializer) {
|
|
@@ -9541,7 +10963,7 @@ function expressionLooksLikeBrowserGlobalBase64Helper(expression, sourceFile, na
|
|
|
9541
10963
|
}
|
|
9542
10964
|
function expressionLooksLikeBrowserGlobalUriCodecHelper(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9543
10965
|
if (!expression) return false;
|
|
9544
|
-
if (
|
|
10966
|
+
if (ts3.isIdentifier(expression) && [
|
|
9545
10967
|
"decodeURI",
|
|
9546
10968
|
"decodeURIComponent",
|
|
9547
10969
|
"encodeURI",
|
|
@@ -9576,7 +10998,7 @@ function expressionLooksLikeBrowserGlobalUriCodecHelper(expression, sourceFile,
|
|
|
9576
10998
|
)) {
|
|
9577
10999
|
return true;
|
|
9578
11000
|
}
|
|
9579
|
-
if (
|
|
11001
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9580
11002
|
return expressionLooksLikeBrowserGlobalUriCodecHelper(
|
|
9581
11003
|
expression.expression,
|
|
9582
11004
|
sourceFile,
|
|
@@ -9585,7 +11007,7 @@ function expressionLooksLikeBrowserGlobalUriCodecHelper(expression, sourceFile,
|
|
|
9585
11007
|
seenIdentifiers
|
|
9586
11008
|
);
|
|
9587
11009
|
}
|
|
9588
|
-
if (
|
|
11010
|
+
if (ts3.isIdentifier(expression)) {
|
|
9589
11011
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9590
11012
|
const initializer = namedExpressions.get(expression.text);
|
|
9591
11013
|
if (initializer) {
|
|
@@ -9624,7 +11046,7 @@ function expressionLooksLikeLocationUrlSource(expression, sourceFile, namedExpre
|
|
|
9624
11046
|
if (LOCATION_URL_SOURCE_REGEX.test(expression.getText(sourceFile))) {
|
|
9625
11047
|
return true;
|
|
9626
11048
|
}
|
|
9627
|
-
if (
|
|
11049
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9628
11050
|
return expressionLooksLikeLocationUrlSource(
|
|
9629
11051
|
expression.expression,
|
|
9630
11052
|
sourceFile,
|
|
@@ -9633,7 +11055,7 @@ function expressionLooksLikeLocationUrlSource(expression, sourceFile, namedExpre
|
|
|
9633
11055
|
seenIdentifiers
|
|
9634
11056
|
);
|
|
9635
11057
|
}
|
|
9636
|
-
if (
|
|
11058
|
+
if (ts3.isNewExpression(expression) && ts3.isIdentifier(expression.expression) && expression.expression.text === "URL") {
|
|
9637
11059
|
return expressionLooksLikeLocationUrlInput(
|
|
9638
11060
|
expression.arguments?.[0],
|
|
9639
11061
|
sourceFile,
|
|
@@ -9642,7 +11064,7 @@ function expressionLooksLikeLocationUrlSource(expression, sourceFile, namedExpre
|
|
|
9642
11064
|
seenIdentifiers
|
|
9643
11065
|
);
|
|
9644
11066
|
}
|
|
9645
|
-
if (
|
|
11067
|
+
if (ts3.isIdentifier(expression)) {
|
|
9646
11068
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9647
11069
|
const initializer = namedExpressions.get(expression.text);
|
|
9648
11070
|
if (!initializer) return false;
|
|
@@ -9664,7 +11086,7 @@ function expressionLooksLikeLocationUrlInput(expression, sourceFile, namedExpres
|
|
|
9664
11086
|
if (LOCATION_URL_INPUT_REGEX.test(expression.getText(sourceFile))) {
|
|
9665
11087
|
return true;
|
|
9666
11088
|
}
|
|
9667
|
-
if (
|
|
11089
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9668
11090
|
return expressionLooksLikeLocationUrlInput(
|
|
9669
11091
|
expression.expression,
|
|
9670
11092
|
sourceFile,
|
|
@@ -9682,7 +11104,7 @@ function expressionLooksLikeLocationUrlInput(expression, sourceFile, namedExpres
|
|
|
9682
11104
|
)) {
|
|
9683
11105
|
return true;
|
|
9684
11106
|
}
|
|
9685
|
-
if (isCallLikeExpression(expression) &&
|
|
11107
|
+
if (isCallLikeExpression(expression) && ts3.isIdentifier(expression.expression) && expression.expression.text === "String" && expression.arguments.length > 0 && (expressionLooksLikeLocationObjectSource(
|
|
9686
11108
|
expression.arguments[0],
|
|
9687
11109
|
sourceFile,
|
|
9688
11110
|
namedExpressions,
|
|
@@ -9735,7 +11157,7 @@ function expressionLooksLikeLocationUrlInput(expression, sourceFile, namedExpres
|
|
|
9735
11157
|
)) {
|
|
9736
11158
|
return true;
|
|
9737
11159
|
}
|
|
9738
|
-
if (
|
|
11160
|
+
if (ts3.isIdentifier(expression)) {
|
|
9739
11161
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9740
11162
|
const initializer = namedExpressions.get(expression.text);
|
|
9741
11163
|
if (initializer) {
|
|
@@ -9782,7 +11204,7 @@ function expressionLooksLikeRequestObjectSource(expression, sourceFile, namedExp
|
|
|
9782
11204
|
if (REQUEST_OBJECT_SOURCE_REGEX.test(expression.getText(sourceFile))) {
|
|
9783
11205
|
return true;
|
|
9784
11206
|
}
|
|
9785
|
-
if (
|
|
11207
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9786
11208
|
return expressionLooksLikeRequestObjectSource(
|
|
9787
11209
|
expression.expression,
|
|
9788
11210
|
sourceFile,
|
|
@@ -9790,7 +11212,7 @@ function expressionLooksLikeRequestObjectSource(expression, sourceFile, namedExp
|
|
|
9790
11212
|
seenIdentifiers
|
|
9791
11213
|
);
|
|
9792
11214
|
}
|
|
9793
|
-
if (
|
|
11215
|
+
if (ts3.isIdentifier(expression)) {
|
|
9794
11216
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9795
11217
|
const initializer = namedExpressions.get(expression.text);
|
|
9796
11218
|
if (!initializer) return false;
|
|
@@ -9811,7 +11233,7 @@ function expressionLooksLikeNextUrlSource(expression, sourceFile, namedExpressio
|
|
|
9811
11233
|
if (NEXT_URL_SOURCE_REGEX.test(expression.getText(sourceFile))) {
|
|
9812
11234
|
return true;
|
|
9813
11235
|
}
|
|
9814
|
-
if (
|
|
11236
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9815
11237
|
return expressionLooksLikeNextUrlSource(
|
|
9816
11238
|
expression.expression,
|
|
9817
11239
|
sourceFile,
|
|
@@ -9837,7 +11259,7 @@ function expressionLooksLikeNextUrlSource(expression, sourceFile, namedExpressio
|
|
|
9837
11259
|
seenIdentifiers
|
|
9838
11260
|
);
|
|
9839
11261
|
}
|
|
9840
|
-
if (
|
|
11262
|
+
if (ts3.isIdentifier(expression)) {
|
|
9841
11263
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9842
11264
|
const initializer = namedExpressions.get(expression.text);
|
|
9843
11265
|
if (initializer) {
|
|
@@ -9871,7 +11293,7 @@ function expressionLooksLikeOpenRedirectSearchParamsCarrier(expression, sourceFi
|
|
|
9871
11293
|
if (/\b(?:searchParams|(?:request|req)\.nextUrl\.searchParams|url\.searchParams)\b/i.test(text)) {
|
|
9872
11294
|
return true;
|
|
9873
11295
|
}
|
|
9874
|
-
if (
|
|
11296
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9875
11297
|
return expressionLooksLikeOpenRedirectSearchParamsCarrier(
|
|
9876
11298
|
expression.expression,
|
|
9877
11299
|
sourceFile,
|
|
@@ -9880,7 +11302,7 @@ function expressionLooksLikeOpenRedirectSearchParamsCarrier(expression, sourceFi
|
|
|
9880
11302
|
seenIdentifiers
|
|
9881
11303
|
);
|
|
9882
11304
|
}
|
|
9883
|
-
if (
|
|
11305
|
+
if (ts3.isNewExpression(expression) && ts3.isIdentifier(expression.expression) && expression.expression.text === "URLSearchParams") {
|
|
9884
11306
|
return expressionLooksLikeLocationQuerySource(
|
|
9885
11307
|
expression.arguments?.[0],
|
|
9886
11308
|
sourceFile,
|
|
@@ -9889,7 +11311,7 @@ function expressionLooksLikeOpenRedirectSearchParamsCarrier(expression, sourceFi
|
|
|
9889
11311
|
seenIdentifiers
|
|
9890
11312
|
);
|
|
9891
11313
|
}
|
|
9892
|
-
if (isCallLikeExpression(expression) && (
|
|
11314
|
+
if (isCallLikeExpression(expression) && (ts3.isIdentifier(expression.expression) && expression.expression.text === "useSearchParams" || isMemberAccessExpression(expression.expression) && isMemberAccessNamed(expression.expression, "useSearchParams"))) {
|
|
9893
11315
|
return true;
|
|
9894
11316
|
}
|
|
9895
11317
|
if (isPropertyLikeAccessExpression(expression) && isPropertyNamed(expression.name, "searchParams")) {
|
|
@@ -9907,7 +11329,7 @@ function expressionLooksLikeOpenRedirectSearchParamsCarrier(expression, sourceFi
|
|
|
9907
11329
|
seenIdentifiers
|
|
9908
11330
|
);
|
|
9909
11331
|
}
|
|
9910
|
-
if (
|
|
11332
|
+
if (ts3.isIdentifier(expression)) {
|
|
9911
11333
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
9912
11334
|
const initializer = namedExpressions.get(expression.text);
|
|
9913
11335
|
if (initializer) {
|
|
@@ -9947,7 +11369,7 @@ function expressionLooksLikeLocationAssignmentTarget(expression, sourceFile, nam
|
|
|
9947
11369
|
if (isLocationAssignmentTarget(expression)) {
|
|
9948
11370
|
return true;
|
|
9949
11371
|
}
|
|
9950
|
-
if (
|
|
11372
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9951
11373
|
return expressionLooksLikeLocationAssignmentTarget(
|
|
9952
11374
|
expression.expression,
|
|
9953
11375
|
sourceFile,
|
|
@@ -9969,7 +11391,7 @@ function expressionLooksLikeLocationAssignmentTarget(expression, sourceFile, nam
|
|
|
9969
11391
|
}
|
|
9970
11392
|
function expressionLooksLikeLocationMutationCall(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
9971
11393
|
if (!expression) return false;
|
|
9972
|
-
if (
|
|
11394
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
9973
11395
|
return expressionLooksLikeLocationMutationCall(
|
|
9974
11396
|
expression.expression,
|
|
9975
11397
|
sourceFile,
|
|
@@ -9996,7 +11418,7 @@ function expressionLooksLikeLocationMutationCall(expression, sourceFile, namedEx
|
|
|
9996
11418
|
seenIdentifiers
|
|
9997
11419
|
);
|
|
9998
11420
|
}
|
|
9999
|
-
if (
|
|
11421
|
+
if (ts3.isIdentifier(expression)) {
|
|
10000
11422
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
10001
11423
|
const initializer = namedExpressions.get(expression.text);
|
|
10002
11424
|
if (initializer) {
|
|
@@ -10035,7 +11457,7 @@ function expressionLooksLikeLocationMutationCall(expression, sourceFile, namedEx
|
|
|
10035
11457
|
}
|
|
10036
11458
|
function expressionLooksLikeWindowOpenCall(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
10037
11459
|
if (!expression) return false;
|
|
10038
|
-
if (
|
|
11460
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
10039
11461
|
return expressionLooksLikeWindowOpenCall(
|
|
10040
11462
|
expression.expression,
|
|
10041
11463
|
sourceFile,
|
|
@@ -10053,10 +11475,10 @@ function expressionLooksLikeWindowOpenCall(expression, sourceFile, namedExpressi
|
|
|
10053
11475
|
seenIdentifiers
|
|
10054
11476
|
);
|
|
10055
11477
|
}
|
|
10056
|
-
if (
|
|
11478
|
+
if (ts3.isIdentifier(expression) && expression.text === "open") {
|
|
10057
11479
|
return true;
|
|
10058
11480
|
}
|
|
10059
|
-
if (
|
|
11481
|
+
if (ts3.isIdentifier(expression)) {
|
|
10060
11482
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
10061
11483
|
const initializer = namedExpressions.get(expression.text);
|
|
10062
11484
|
if (initializer) {
|
|
@@ -10092,7 +11514,7 @@ function expressionLooksLikeWindowOpenCall(expression, sourceFile, namedExpressi
|
|
|
10092
11514
|
}
|
|
10093
11515
|
function expressionLooksLikeHistoryMutationCall(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
10094
11516
|
if (!expression) return false;
|
|
10095
|
-
if (
|
|
11517
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
10096
11518
|
return expressionLooksLikeHistoryMutationCall(
|
|
10097
11519
|
expression.expression,
|
|
10098
11520
|
sourceFile,
|
|
@@ -10119,7 +11541,7 @@ function expressionLooksLikeHistoryMutationCall(expression, sourceFile, namedExp
|
|
|
10119
11541
|
seenIdentifiers
|
|
10120
11542
|
);
|
|
10121
11543
|
}
|
|
10122
|
-
if (
|
|
11544
|
+
if (ts3.isIdentifier(expression)) {
|
|
10123
11545
|
if (seenIdentifiers.has(expression.text)) return false;
|
|
10124
11546
|
const initializer = namedExpressions.get(expression.text);
|
|
10125
11547
|
if (initializer) {
|
|
@@ -10158,7 +11580,7 @@ function expressionLooksLikeHistoryMutationCall(expression, sourceFile, namedExp
|
|
|
10158
11580
|
}
|
|
10159
11581
|
function expressionLooksLikeRouteTransitionCall(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers) {
|
|
10160
11582
|
if (!expression) return false;
|
|
10161
|
-
if (
|
|
11583
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
10162
11584
|
return expressionLooksLikeRouteTransitionCall(
|
|
10163
11585
|
expression.expression,
|
|
10164
11586
|
sourceFile,
|
|
@@ -10185,7 +11607,7 @@ function expressionLooksLikeRouteTransitionCall(expression, sourceFile, namedExp
|
|
|
10185
11607
|
seenIdentifiers
|
|
10186
11608
|
);
|
|
10187
11609
|
}
|
|
10188
|
-
if (
|
|
11610
|
+
if (ts3.isIdentifier(expression)) {
|
|
10189
11611
|
if (["redirect", "navigate"].includes(expression.text)) {
|
|
10190
11612
|
return true;
|
|
10191
11613
|
}
|
|
@@ -10231,7 +11653,7 @@ function expressionLooksLikeRouteTransitionCall(expression, sourceFile, namedExp
|
|
|
10231
11653
|
);
|
|
10232
11654
|
}
|
|
10233
11655
|
function getRouteTransitionTargetExpression(node, sourceFile, namedExpressions, namedPropertyAliases) {
|
|
10234
|
-
if (isMemberAccessExpression(node.expression) &&
|
|
11656
|
+
if (isMemberAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "Reflect" && isMemberAccessNamed(node.expression, "apply") && expressionLooksLikeRouteTransitionCall(
|
|
10235
11657
|
node.arguments[0],
|
|
10236
11658
|
sourceFile,
|
|
10237
11659
|
namedExpressions,
|
|
@@ -10276,7 +11698,7 @@ function getRouteTransitionTargetExpression(node, sourceFile, namedExpressions,
|
|
|
10276
11698
|
)) {
|
|
10277
11699
|
return getAliasedApplyArgumentExpression(node.arguments[1], 2, namedExpressions, /* @__PURE__ */ new Set());
|
|
10278
11700
|
}
|
|
10279
|
-
if (isMemberAccessExpression(node.expression) &&
|
|
11701
|
+
if (isMemberAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "Reflect" && isMemberAccessNamed(node.expression, "apply") && expressionLooksLikeHistoryMutationCall(
|
|
10280
11702
|
node.arguments[0],
|
|
10281
11703
|
sourceFile,
|
|
10282
11704
|
namedExpressions,
|
|
@@ -10307,7 +11729,7 @@ function getRouteTransitionTargetExpression(node, sourceFile, namedExpressions,
|
|
|
10307
11729
|
}
|
|
10308
11730
|
function getAliasedApplyArgumentExpression(expression, index, namedExpressions, seenIdentifiers) {
|
|
10309
11731
|
if (!expression) return void 0;
|
|
10310
|
-
if (
|
|
11732
|
+
if (ts3.isParenthesizedExpression(expression) || ts3.isAsExpression(expression) || ts3.isTypeAssertionExpression(expression) || ts3.isNonNullExpression(expression)) {
|
|
10311
11733
|
return getAliasedApplyArgumentExpression(
|
|
10312
11734
|
expression.expression,
|
|
10313
11735
|
index,
|
|
@@ -10315,7 +11737,7 @@ function getAliasedApplyArgumentExpression(expression, index, namedExpressions,
|
|
|
10315
11737
|
seenIdentifiers
|
|
10316
11738
|
);
|
|
10317
11739
|
}
|
|
10318
|
-
if (
|
|
11740
|
+
if (ts3.isIdentifier(expression)) {
|
|
10319
11741
|
if (seenIdentifiers.has(expression.text)) return void 0;
|
|
10320
11742
|
const initializer = namedExpressions.get(expression.text);
|
|
10321
11743
|
if (!initializer) return void 0;
|
|
@@ -10329,12 +11751,12 @@ function getAliasedApplyArgumentExpression(expression, index, namedExpressions,
|
|
|
10329
11751
|
seenIdentifiers.delete(expression.text);
|
|
10330
11752
|
return result;
|
|
10331
11753
|
}
|
|
10332
|
-
if (!
|
|
11754
|
+
if (!ts3.isArrayLiteralExpression(expression)) return void 0;
|
|
10333
11755
|
const argument = expression.elements[index];
|
|
10334
|
-
return
|
|
11756
|
+
return ts3.isExpression(argument) ? argument : void 0;
|
|
10335
11757
|
}
|
|
10336
11758
|
function getLocationMutationTargetExpression(node, sourceFile, namedExpressions, namedPropertyAliases) {
|
|
10337
|
-
if (isMemberAccessExpression(node.expression) &&
|
|
11759
|
+
if (isMemberAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "Reflect" && isMemberAccessNamed(node.expression, "apply") && expressionLooksLikeLocationMutationCall(
|
|
10338
11760
|
node.arguments[0],
|
|
10339
11761
|
sourceFile,
|
|
10340
11762
|
namedExpressions,
|
|
@@ -10373,7 +11795,7 @@ function getLocationMutationTargetExpression(node, sourceFile, namedExpressions,
|
|
|
10373
11795
|
return void 0;
|
|
10374
11796
|
}
|
|
10375
11797
|
function getWindowOpenTargetExpression(node, sourceFile, namedExpressions, namedPropertyAliases) {
|
|
10376
|
-
if (isMemberAccessExpression(node.expression) &&
|
|
11798
|
+
if (isMemberAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "Reflect" && isMemberAccessNamed(node.expression, "apply") && expressionLooksLikeWindowOpenCall(
|
|
10377
11799
|
node.arguments[0],
|
|
10378
11800
|
sourceFile,
|
|
10379
11801
|
namedExpressions,
|
|
@@ -10512,28 +11934,28 @@ var DYNAMIC_GEOMETRY_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
|
10512
11934
|
"y"
|
|
10513
11935
|
]);
|
|
10514
11936
|
function getJsxAttributeExpression(initializer) {
|
|
10515
|
-
if (!initializer || !
|
|
11937
|
+
if (!initializer || !ts3.isJsxExpression(initializer)) return null;
|
|
10516
11938
|
return initializer.expression ?? null;
|
|
10517
11939
|
}
|
|
10518
11940
|
function getStylePropertyName(name) {
|
|
10519
|
-
if (
|
|
11941
|
+
if (ts3.isIdentifier(name) || ts3.isStringLiteral(name) || ts3.isNumericLiteral(name)) {
|
|
10520
11942
|
return name.text;
|
|
10521
11943
|
}
|
|
10522
|
-
if (
|
|
11944
|
+
if (ts3.isComputedPropertyName(name)) {
|
|
10523
11945
|
return getExpressionLiteralValue(name.expression);
|
|
10524
11946
|
}
|
|
10525
11947
|
return null;
|
|
10526
11948
|
}
|
|
10527
11949
|
function unwrapStyleExpression(expression) {
|
|
10528
11950
|
let current = expression;
|
|
10529
|
-
while (
|
|
11951
|
+
while (ts3.isParenthesizedExpression(current) || ts3.isAsExpression(current) || ts3.isTypeAssertionExpression(current) || ts3.isNonNullExpression(current) || ts3.isSatisfiesExpression(current)) {
|
|
10530
11952
|
current = current.expression;
|
|
10531
11953
|
}
|
|
10532
11954
|
return current;
|
|
10533
11955
|
}
|
|
10534
11956
|
function isDynamicStyleValue(expression) {
|
|
10535
11957
|
const unwrapped = unwrapStyleExpression(expression);
|
|
10536
|
-
if (
|
|
11958
|
+
if (ts3.isStringLiteral(unwrapped) || ts3.isNoSubstitutionTemplateLiteral(unwrapped) || ts3.isNumericLiteral(unwrapped) || unwrapped.kind === ts3.SyntaxKind.TrueKeyword || unwrapped.kind === ts3.SyntaxKind.FalseKeyword || unwrapped.kind === ts3.SyntaxKind.NullKeyword) {
|
|
10537
11959
|
return false;
|
|
10538
11960
|
}
|
|
10539
11961
|
return true;
|
|
@@ -10542,13 +11964,13 @@ function isAllowedInlineStyleObject(objectLiteral) {
|
|
|
10542
11964
|
let sawGeometryProperty = false;
|
|
10543
11965
|
let sawDynamicGeometryValue = false;
|
|
10544
11966
|
for (const property of objectLiteral.properties) {
|
|
10545
|
-
if (!
|
|
10546
|
-
const
|
|
10547
|
-
if (!
|
|
10548
|
-
if (
|
|
11967
|
+
if (!ts3.isPropertyAssignment(property)) return false;
|
|
11968
|
+
const propertyName2 = getStylePropertyName(property.name);
|
|
11969
|
+
if (!propertyName2) return false;
|
|
11970
|
+
if (propertyName2.startsWith("--d-")) {
|
|
10549
11971
|
continue;
|
|
10550
11972
|
}
|
|
10551
|
-
if (!DYNAMIC_GEOMETRY_STYLE_PROPS.has(
|
|
11973
|
+
if (!DYNAMIC_GEOMETRY_STYLE_PROPS.has(propertyName2)) {
|
|
10552
11974
|
return false;
|
|
10553
11975
|
}
|
|
10554
11976
|
sawGeometryProperty = true;
|
|
@@ -10571,10 +11993,10 @@ function isAllowedInlineStyleAttribute(attribute, sourceFile, namedExpressions,
|
|
|
10571
11993
|
return resolved ? isAllowedInlineStyleObject(resolved.objectLiteral) : false;
|
|
10572
11994
|
}
|
|
10573
11995
|
function analyzeAstSignals(filePath, code) {
|
|
10574
|
-
const sourceFile =
|
|
11996
|
+
const sourceFile = ts3.createSourceFile(
|
|
10575
11997
|
filePath,
|
|
10576
11998
|
code,
|
|
10577
|
-
|
|
11999
|
+
ts3.ScriptTarget.Latest,
|
|
10578
12000
|
true,
|
|
10579
12001
|
getScriptKind(filePath)
|
|
10580
12002
|
);
|
|
@@ -10659,7 +12081,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10659
12081
|
let navigationLandmarkCount = 0;
|
|
10660
12082
|
let unlabeledNavigationLandmarkCount = 0;
|
|
10661
12083
|
const walk = (node) => {
|
|
10662
|
-
if (
|
|
12084
|
+
if (ts3.isJsxAttribute(node)) {
|
|
10663
12085
|
if (isPropertyNamed(node.name, "style") && node.initializer) {
|
|
10664
12086
|
if (!isAllowedInlineStyleAttribute(
|
|
10665
12087
|
node,
|
|
@@ -10673,7 +12095,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10673
12095
|
if (isPropertyNamed(node.name, "dangerouslySetInnerHTML") && !isSafeJsonLdDangerouslySetInnerHtml(node)) {
|
|
10674
12096
|
signals.dangerousHtmlCount += 1;
|
|
10675
12097
|
}
|
|
10676
|
-
if (isPropertyNamed(node.name, "href", "to") && node.initializer &&
|
|
12098
|
+
if (isPropertyNamed(node.name, "href", "to") && node.initializer && ts3.isJsxExpression(node.initializer) && expressionContainsOpenRedirectSource(
|
|
10677
12099
|
node.initializer.expression,
|
|
10678
12100
|
sourceFile,
|
|
10679
12101
|
namedExpressionInitializers,
|
|
@@ -10691,10 +12113,10 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10691
12113
|
signals.authProviderNonceMissingCount += 1;
|
|
10692
12114
|
}
|
|
10693
12115
|
}
|
|
10694
|
-
if (
|
|
12116
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && ts3.isPropertyAccessExpression(node.left) && isPropertyNamed(node.left.name, "innerHTML", "outerHTML")) {
|
|
10695
12117
|
signals.rawHtmlInjectionCount += 1;
|
|
10696
12118
|
}
|
|
10697
|
-
if (
|
|
12119
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && expressionLooksLikeLocationAssignmentTarget(
|
|
10698
12120
|
node.left,
|
|
10699
12121
|
sourceFile,
|
|
10700
12122
|
namedExpressionInitializers,
|
|
@@ -10708,7 +12130,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10708
12130
|
)) {
|
|
10709
12131
|
signals.authOpenRedirectSignalCount += 1;
|
|
10710
12132
|
}
|
|
10711
|
-
if (
|
|
12133
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && expressionLooksLikeLocationAssignmentTarget(
|
|
10712
12134
|
node.left,
|
|
10713
12135
|
sourceFile,
|
|
10714
12136
|
namedExpressionInitializers,
|
|
@@ -10717,7 +12139,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10717
12139
|
) && isInsecureTransportUrl(getExpressionLiteralValue(node.right))) {
|
|
10718
12140
|
signals.insecureTransportEndpointCount += 1;
|
|
10719
12141
|
}
|
|
10720
|
-
if (
|
|
12142
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && expressionLooksLikeLocationAssignmentTarget(
|
|
10721
12143
|
node.left,
|
|
10722
12144
|
sourceFile,
|
|
10723
12145
|
namedExpressionInitializers,
|
|
@@ -10735,7 +12157,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10735
12157
|
signals.authProviderNonceMissingCount += 1;
|
|
10736
12158
|
}
|
|
10737
12159
|
}
|
|
10738
|
-
if (
|
|
12160
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && expressionLooksLikeLocationAssignmentTarget(
|
|
10739
12161
|
node.left,
|
|
10740
12162
|
sourceFile,
|
|
10741
12163
|
namedExpressionInitializers,
|
|
@@ -10749,36 +12171,36 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10749
12171
|
)) {
|
|
10750
12172
|
signals.authOpenRedirectSignalCount += 1;
|
|
10751
12173
|
}
|
|
10752
|
-
if (
|
|
12174
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && ts3.isPropertyAccessExpression(node.left) && isPropertyNamed(node.left.name, "onmessage")) {
|
|
10753
12175
|
const handler = resolveFunctionLikeHandler(node.right, namedFunctionDeclarations);
|
|
10754
12176
|
if (handler && !functionLikeReferencesOrigin(handler)) {
|
|
10755
12177
|
signals.messageListenerWithoutOriginCheckCount += 1;
|
|
10756
12178
|
}
|
|
10757
12179
|
}
|
|
10758
|
-
if (
|
|
12180
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && ts3.isPropertyAccessExpression(node.left) && isBrowserStorageObject(node.left.expression) && /(?:token|auth|jwt|session)/i.test(node.left.name.text)) {
|
|
10759
12181
|
signals.authStorageWriteCount += 1;
|
|
10760
12182
|
}
|
|
10761
|
-
if (
|
|
12183
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && ts3.isElementAccessExpression(node.left) && isBrowserStorageObject(node.left.expression) && isAuthStorageKeyLiteral(node.left.argumentExpression)) {
|
|
10762
12184
|
signals.authStorageWriteCount += 1;
|
|
10763
12185
|
}
|
|
10764
|
-
if (
|
|
12186
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && isCookiePropertyAccess(node.left) && hasAuthCredentialText(node.right, sourceFile)) {
|
|
10765
12187
|
signals.authCookieWriteCount += 1;
|
|
10766
12188
|
}
|
|
10767
|
-
if (
|
|
12189
|
+
if (ts3.isBinaryExpression(node) && node.operatorToken.kind === ts3.SyntaxKind.EqualsToken && isAuthorizationHeaderAccess(node.left)) {
|
|
10768
12190
|
if (isHeaderClearValue(node.right)) {
|
|
10769
12191
|
signals.authHeaderClearCount += 1;
|
|
10770
12192
|
} else {
|
|
10771
12193
|
signals.authHeaderWriteCount += 1;
|
|
10772
12194
|
}
|
|
10773
12195
|
}
|
|
10774
|
-
if (
|
|
12196
|
+
if (ts3.isPropertyAssignment(node) && isAuthorizationHeaderName(node.name)) {
|
|
10775
12197
|
if (isHeaderClearValue(node.initializer)) {
|
|
10776
12198
|
signals.authHeaderClearCount += 1;
|
|
10777
12199
|
} else {
|
|
10778
12200
|
signals.authHeaderWriteCount += 1;
|
|
10779
12201
|
}
|
|
10780
12202
|
}
|
|
10781
|
-
if (
|
|
12203
|
+
if (ts3.isDeleteExpression(node) && isAuthorizationHeaderAccess(node.expression)) {
|
|
10782
12204
|
signals.authHeaderClearCount += 1;
|
|
10783
12205
|
}
|
|
10784
12206
|
if (isCallLikeExpression(node)) {
|
|
@@ -10809,10 +12231,10 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10809
12231
|
namedPropertyAliases
|
|
10810
12232
|
);
|
|
10811
12233
|
const windowOpenTargetLiteral = getExpressionLiteralValue(windowOpenTargetExpression);
|
|
10812
|
-
if (
|
|
12234
|
+
if (ts3.isIdentifier(node.expression) && node.expression.text === "eval") {
|
|
10813
12235
|
signals.dynamicEvalCount += 1;
|
|
10814
12236
|
}
|
|
10815
|
-
if ((
|
|
12237
|
+
if ((ts3.isIdentifier(node.expression) && ["setTimeout", "setInterval"].includes(node.expression.text) || ts3.isPropertyAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "window" && isPropertyNamed(node.expression.name, "setTimeout", "setInterval")) && typeof firstArgumentLiteral === "string") {
|
|
10816
12238
|
signals.dynamicEvalCount += 1;
|
|
10817
12239
|
}
|
|
10818
12240
|
if ((isFetchLikeCall(node) || isAxiosLikeCall(node)) && isInsecureTransportUrl(firstArgumentLiteral)) {
|
|
@@ -10894,7 +12316,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10894
12316
|
signals.authProviderNonceMissingCount += 1;
|
|
10895
12317
|
}
|
|
10896
12318
|
}
|
|
10897
|
-
if (
|
|
12319
|
+
if (ts3.isPropertyAccessExpression(node.expression) && isPropertyNamed(node.expression.name, "postMessage") && secondArgumentLiteral === "*") {
|
|
10898
12320
|
signals.wildcardPostMessageCount += 1;
|
|
10899
12321
|
}
|
|
10900
12322
|
if (isAddEventListenerCall(node) && firstArgumentLiteral === "message") {
|
|
@@ -10903,7 +12325,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10903
12325
|
signals.messageListenerWithoutOriginCheckCount += 1;
|
|
10904
12326
|
}
|
|
10905
12327
|
}
|
|
10906
|
-
if (
|
|
12328
|
+
if (ts3.isPropertyAccessExpression(node.expression) && isPropertyNamed(node.expression.name, "setHeader", "append", "set") && isSetCookieHeaderName(node.arguments[0])) {
|
|
10907
12329
|
const cookieStrings = collectCookieHeaderStrings(node.arguments[1]);
|
|
10908
12330
|
const authCookieStrings = cookieStrings.filter(cookieHeaderStringLooksAuthLike);
|
|
10909
12331
|
if (authCookieStrings.length > 0) {
|
|
@@ -10921,13 +12343,13 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10921
12343
|
}
|
|
10922
12344
|
}
|
|
10923
12345
|
}
|
|
10924
|
-
if ((
|
|
12346
|
+
if ((ts3.isIdentifier(node.expression) && node.expression.text === "open" || ts3.isPropertyAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "window" && isPropertyNamed(node.expression.name, "open")) && secondArgumentLiteral === "_blank") {
|
|
10925
12347
|
const featureLiteral = getExpressionLiteralValue(node.arguments[2])?.toLowerCase() ?? "";
|
|
10926
12348
|
if (!featureLiteral.includes("noopener") || !featureLiteral.includes("noreferrer")) {
|
|
10927
12349
|
signals.windowOpenWithoutNoopenerCount += 1;
|
|
10928
12350
|
}
|
|
10929
12351
|
}
|
|
10930
|
-
if (
|
|
12352
|
+
if (ts3.isPropertyAccessExpression(node.expression)) {
|
|
10931
12353
|
if (isPropertyNamed(node.expression.name, "insertAdjacentHTML")) {
|
|
10932
12354
|
signals.rawHtmlInjectionCount += 1;
|
|
10933
12355
|
}
|
|
@@ -10936,7 +12358,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10936
12358
|
}
|
|
10937
12359
|
if (isCookieMutationSetCall(node)) {
|
|
10938
12360
|
const firstArgument = node.arguments[0];
|
|
10939
|
-
const isObjectConfigCall =
|
|
12361
|
+
const isObjectConfigCall = ts3.isObjectLiteralExpression(firstArgument);
|
|
10940
12362
|
const isAuthCookieWrite = isObjectConfigCall ? objectLiteralLooksLikeAuthCookieConfig(firstArgument, sourceFile) : expressionLooksLikeAuthCookieName(firstArgument, sourceFile);
|
|
10941
12363
|
if (isAuthCookieWrite) {
|
|
10942
12364
|
signals.authCookieWriteCount += 1;
|
|
@@ -10956,7 +12378,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10956
12378
|
signals.authHeaderWriteCount += 1;
|
|
10957
12379
|
}
|
|
10958
12380
|
}
|
|
10959
|
-
if (
|
|
12381
|
+
if (ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "document" && isPropertyNamed(node.expression.name, "write")) {
|
|
10960
12382
|
signals.rawHtmlInjectionCount += 1;
|
|
10961
12383
|
}
|
|
10962
12384
|
if (isPropertyNamed(node.expression.name, "eval")) {
|
|
@@ -10964,16 +12386,16 @@ function analyzeAstSignals(filePath, code) {
|
|
|
10964
12386
|
}
|
|
10965
12387
|
}
|
|
10966
12388
|
}
|
|
10967
|
-
if (
|
|
12389
|
+
if (ts3.isNewExpression(node) && isRealtimeTransportConstructor(node) && isInsecureTransportUrl(getExpressionLiteralValue(node.arguments?.[0]))) {
|
|
10968
12390
|
signals.insecureTransportEndpointCount += 1;
|
|
10969
12391
|
}
|
|
10970
|
-
if (
|
|
12392
|
+
if (ts3.isNewExpression(node) && ts3.isIdentifier(node.expression) && node.expression.text === "Function") {
|
|
10971
12393
|
signals.dynamicEvalCount += 1;
|
|
10972
12394
|
}
|
|
10973
|
-
if (
|
|
12395
|
+
if (ts3.isNewExpression(node) && ts3.isIdentifier(node.expression) && ["WebSocket", "EventSource"].includes(node.expression.text) && isInsecureTransportUrl(getExpressionLiteralValue(node.arguments?.[0]))) {
|
|
10974
12396
|
signals.insecureTransportEndpointCount += 1;
|
|
10975
12397
|
}
|
|
10976
|
-
if (
|
|
12398
|
+
if (ts3.isJsxSelfClosingElement(node)) {
|
|
10977
12399
|
const tagName = getJsxTagName(node);
|
|
10978
12400
|
if (hasMainLandmarkSignal(node.attributes, tagName)) {
|
|
10979
12401
|
signals.mainLandmarkCount += 1;
|
|
@@ -11066,7 +12488,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
11066
12488
|
signals.formControlWithoutLabelCount += 1;
|
|
11067
12489
|
}
|
|
11068
12490
|
}
|
|
11069
|
-
if (
|
|
12491
|
+
if (ts3.isJsxElement(node)) {
|
|
11070
12492
|
const tagName = getJsxTagName(node.openingElement);
|
|
11071
12493
|
const textContent = getJsxTextContent(node).replace(/\s+/g, " ").trim();
|
|
11072
12494
|
if (hasMainLandmarkSignal(node.openingElement.attributes, tagName)) {
|
|
@@ -11182,7 +12604,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
11182
12604
|
signals.formControlWithoutLabelCount += 1;
|
|
11183
12605
|
}
|
|
11184
12606
|
}
|
|
11185
|
-
|
|
12607
|
+
ts3.forEachChild(node, walk);
|
|
11186
12608
|
};
|
|
11187
12609
|
walk(sourceFile);
|
|
11188
12610
|
signals.unlabeledNavigationLandmarkCount = navigationLandmarkCount > 1 ? unlabeledNavigationLandmarkCount : 0;
|
|
@@ -12936,10 +14358,10 @@ function critiqueSource({
|
|
|
12936
14358
|
};
|
|
12937
14359
|
}
|
|
12938
14360
|
function resolveProjectFilePath(projectRoot, filePath) {
|
|
12939
|
-
const root =
|
|
14361
|
+
const root = existsSync4(projectRoot) ? realpathSync.native(projectRoot) : resolve(projectRoot);
|
|
12940
14362
|
const candidatePath = isAbsolute2(filePath) ? resolve(filePath) : resolve(root, filePath);
|
|
12941
|
-
const resolvedPath =
|
|
12942
|
-
const relativePath =
|
|
14363
|
+
const resolvedPath = existsSync4(candidatePath) ? realpathSync.native(candidatePath) : candidatePath;
|
|
14364
|
+
const relativePath = relative4(root, resolvedPath);
|
|
12943
14365
|
if (relativePath.startsWith("..") || isAbsolute2(relativePath)) {
|
|
12944
14366
|
throw new Error(`Path escapes the project root: ${filePath}`);
|
|
12945
14367
|
}
|
|
@@ -12948,7 +14370,7 @@ function resolveProjectFilePath(projectRoot, filePath) {
|
|
|
12948
14370
|
async function critiqueFile(filePath, projectRoot) {
|
|
12949
14371
|
const resolvedPath = resolveProjectFilePath(projectRoot, filePath);
|
|
12950
14372
|
const code = await readFile(resolvedPath, "utf-8");
|
|
12951
|
-
const treatmentsCss = readTextIfExists(
|
|
14373
|
+
const treatmentsCss = readTextIfExists(join4(projectRoot, "src", "styles", "treatments.css"));
|
|
12952
14374
|
const reviewPack = loadReviewPack(projectRoot);
|
|
12953
14375
|
const packManifest = loadPackManifest(projectRoot);
|
|
12954
14376
|
const adoptionMode = readProjectAdoptionMode(projectRoot);
|
|
@@ -12962,23 +14384,34 @@ async function critiqueFile(filePath, projectRoot) {
|
|
|
12962
14384
|
});
|
|
12963
14385
|
}
|
|
12964
14386
|
export {
|
|
14387
|
+
COMPONENT_REUSE_RULE_ID,
|
|
12965
14388
|
EVIDENCE_BUNDLE_SCHEMA_URL,
|
|
12966
14389
|
INTERACTION_SIGNALS,
|
|
14390
|
+
KNOWN_VERIFICATION_DIAGNOSTICS,
|
|
14391
|
+
RAW_CONTROL_REUSE_RULE_ID,
|
|
12967
14392
|
SCAN_REPORT_SCHEMA_URL,
|
|
14393
|
+
STYLE_BRIDGE_ARBITRARY_VALUE_RULE_ID,
|
|
12968
14394
|
VERIFICATION_SCHEMA_URLS,
|
|
14395
|
+
anchorFindingsToGraph,
|
|
12969
14396
|
auditBuiltDist,
|
|
14397
|
+
auditComponentReuse,
|
|
12970
14398
|
auditProject,
|
|
14399
|
+
auditStyleBridgeDrift,
|
|
14400
|
+
buildProjectHealthRepairPlan,
|
|
12971
14401
|
collectMissingPackManifestFiles,
|
|
14402
|
+
collectProjectSourceFiles,
|
|
12972
14403
|
createContractAssertions,
|
|
12973
14404
|
createEvidenceBundle,
|
|
12974
14405
|
createUnavailableScanReport,
|
|
12975
14406
|
critiqueFile,
|
|
12976
14407
|
critiqueSource,
|
|
14408
|
+
deriveVerificationDiagnostic,
|
|
12977
14409
|
emptyRuntimeAudit,
|
|
12978
14410
|
extractRouteHintsFromEssence,
|
|
12979
14411
|
listKnownInteractions,
|
|
12980
14412
|
probePublishedSite,
|
|
12981
14413
|
resolveGitHubScanInput,
|
|
14414
|
+
resolveGraphAnchorForFinding,
|
|
12982
14415
|
scanProject,
|
|
12983
14416
|
verifyInteractionsInSource
|
|
12984
14417
|
};
|