@groma/scanner-react 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +129 -11
  2. package/dist/index.js +1385 -112
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,8 +1,541 @@
1
1
  // @bun
2
- // plugins/scanners/react/src/scan.ts
3
- import { existsSync as existsSync2, readFileSync } from "fs";
4
- import { createRequire } from "module";
2
+ // plugins/scanners/react/src/index.ts
3
+ import ts5 from "./typescript.cjs";
4
+
5
+ // plugins/scanners/typescript-outline.ts
6
+ import { readFile } from "fs/promises";
7
+ import path from "path";
8
+ function is(node, kind) {
9
+ return node !== undefined && node.kind === kind;
10
+ }
11
+ var NO_EXPORTS = new Set;
12
+ function hasModifier(node, kind) {
13
+ return node.modifiers?.some((modifier) => modifier.kind === kind) ?? false;
14
+ }
15
+ function listedExports(k, statement) {
16
+ if (is(statement, k.ExportAssignment)) {
17
+ return is(statement.expression, k.Identifier) ? [statement.expression.text] : [];
18
+ }
19
+ if (!is(statement, k.ExportDeclaration) || statement.moduleSpecifier !== undefined)
20
+ return [];
21
+ const clause = statement.exportClause;
22
+ if (!is(clause, k.NamedExports))
23
+ return [];
24
+ return clause.elements.map((element) => (element.propertyName ?? element.name).text);
25
+ }
26
+ function topLevelVisibility(scope, name, statement) {
27
+ if (scope.topLevelPrivate)
28
+ return "private";
29
+ return hasModifier(statement, scope.k.ExportKeyword) || scope.exported.has(name) ? "public" : "private";
30
+ }
31
+ function memberVisibility(scope, member) {
32
+ const { k } = scope;
33
+ if (is(member.name, k.PrivateIdentifier) || hasModifier(member, k.PrivateKeyword))
34
+ return "private";
35
+ return hasModifier(member, k.ProtectedKeyword) ? "protected" : "public";
36
+ }
37
+ function symbolAt(scope, name, node, visibility) {
38
+ const line = scope.source.getLineAndCharacterOfPosition(node.getStart()).line + 1;
39
+ return { name, line, visibility, entry: scope.symbols.includes(name) };
40
+ }
41
+ function functionDeclarations(scope, statement) {
42
+ const { k } = scope;
43
+ if (is(statement, k.FunctionDeclaration) && is(statement.name, k.Identifier)) {
44
+ const name = statement.name.text;
45
+ return [{ kind: "function", ...symbolAt(scope, name, statement.name, topLevelVisibility(scope, name, statement)) }];
46
+ }
47
+ if (!is(statement, k.VariableStatement))
48
+ return [];
49
+ return statement.declarationList.declarations.flatMap((declaration) => {
50
+ const { name, initializer } = declaration;
51
+ if (!is(name, k.Identifier))
52
+ return [];
53
+ if (!is(initializer, k.ArrowFunction) && !is(initializer, k.FunctionExpression))
54
+ return [];
55
+ return [{ kind: "function", ...symbolAt(scope, name.text, name, topLevelVisibility(scope, name.text, statement)) }];
56
+ });
57
+ }
58
+ function memberName(scope, name) {
59
+ const { k } = scope;
60
+ if (is(name, k.Identifier) || is(name, k.PrivateIdentifier))
61
+ return name;
62
+ return is(name, k.StringLiteral) || is(name, k.NumericLiteral) ? name : undefined;
63
+ }
64
+ function typeMember(scope, member) {
65
+ const { k } = scope;
66
+ if (member.kind === k.Constructor)
67
+ return [symbolAt(scope, "constructor", member, memberVisibility(scope, member))];
68
+ if (!is(member, k.MethodDeclaration) && !is(member, k.MethodSignature))
69
+ return [];
70
+ const name = memberName(scope, member.name);
71
+ return name === undefined ? [] : [symbolAt(scope, name.text, name, memberVisibility(scope, member))];
72
+ }
73
+ function typeMembers(k, statement) {
74
+ if (is(statement, k.ClassDeclaration) || is(statement, k.InterfaceDeclaration))
75
+ return statement.members;
76
+ return statement.kind === k.EnumDeclaration ? [] : undefined;
77
+ }
78
+ function typeDeclaration(scope, statement) {
79
+ const members = typeMembers(scope.k, statement);
80
+ const { name } = statement;
81
+ if (members === undefined || !is(name, scope.k.Identifier))
82
+ return [];
83
+ return [{
84
+ kind: "type",
85
+ ...symbolAt(scope, name.text, name, topLevelVisibility(scope, name.text, statement)),
86
+ members: members.flatMap((member) => typeMember(scope, member))
87
+ }];
88
+ }
89
+ function moduleStatements(k, statement) {
90
+ let body = statement.body;
91
+ while (is(body, k.ModuleDeclaration))
92
+ body = body.body;
93
+ return is(body, k.ModuleBlock) ? body.statements : [];
94
+ }
95
+ function declarationsIn(scope, statements) {
96
+ return statements.flatMap((statement) => {
97
+ if (is(statement, scope.k.ModuleDeclaration)) {
98
+ return declarationsIn({ ...scope, exported: NO_EXPORTS }, moduleStatements(scope.k, statement));
99
+ }
100
+ return [...functionDeclarations(scope, statement), ...typeDeclaration(scope, statement)];
101
+ });
102
+ }
103
+ function outlineSource(ts, source, context) {
104
+ const k = ts.SyntaxKind;
105
+ const exported = new Set([...source.statements.flatMap((statement) => listedExports(k, statement)), ...context.exported ?? []]);
106
+ const scope = { k, source, symbols: context.symbols, exported, topLevelPrivate: context.topLevelPrivate ?? false };
107
+ return declarationsIn(scope, source.statements);
108
+ }
109
+ function outlineDeclarations(ts, block) {
110
+ return outlineSource(ts, ts.createSourceFile(block.fileName, block.text, ts.ScriptTarget.Latest, true), block);
111
+ }
112
+ async function readTypeScriptOutline(ts, repositoryRoot, references) {
113
+ const files = [];
114
+ for (const reference of references) {
115
+ const fileName = path.join(repositoryRoot, reference.file);
116
+ const text = await readFile(fileName, "utf8");
117
+ const declarations = outlineDeclarations(ts, { fileName, text, symbols: reference.symbols });
118
+ if (declarations.length > 0)
119
+ files.push({ file: reference.file, declarations });
120
+ }
121
+ return files;
122
+ }
123
+
124
+ // plugins/scanners/typescript-project.ts
125
+ import path3 from "path";
126
+
127
+ // plugins/scanners/projects.ts
128
+ import { execFile } from "child_process";
129
+ import { existsSync } from "fs";
130
+ import { readFile as readFile2 } from "fs/promises";
5
131
  import path2 from "path";
132
+ import { promisify } from "util";
133
+ var execute = promisify(execFile);
134
+ var excluded = new Set([
135
+ ".git",
136
+ "node_modules",
137
+ "vendor",
138
+ "target",
139
+ "dist",
140
+ "build",
141
+ "bin",
142
+ "obj",
143
+ ".gradle",
144
+ ".angular",
145
+ "coverage",
146
+ "generated",
147
+ "groma",
148
+ ".groma"
149
+ ]);
150
+ async function repositoryFiles(root, matches) {
151
+ const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
152
+ return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && existsSync(path2.join(root, file))))].sort();
153
+ }
154
+ function projectFiles(root, matches) {
155
+ return repositoryFiles(root, (file) => matches(file) && !file.split("/").some((part) => excluded.has(part)));
156
+ }
157
+ function hasDependency(manifest, dependency) {
158
+ return ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"].some((section) => typeof manifest[section]?.[dependency] === "string");
159
+ }
160
+ async function frameworkProjects(root, dependency, extensions) {
161
+ const files = await projectFiles(root, () => true);
162
+ const manifests = files.filter((file) => path2.posix.basename(file) === "package.json");
163
+ const directories = new Set(manifests.map((file) => path2.posix.dirname(file)));
164
+ const sources = new Set;
165
+ for (const file of files.filter((file) => extensions.some((extension) => file.endsWith(extension)) && !file.endsWith(".d.ts"))) {
166
+ let directory = path2.posix.dirname(file);
167
+ while (directory !== "." && !directories.has(directory))
168
+ directory = path2.posix.dirname(directory);
169
+ sources.add(directory);
170
+ }
171
+ const projects = [];
172
+ for (const file of manifests) {
173
+ const directory = path2.posix.dirname(file);
174
+ if (!sources.has(directory) || !files.includes(path2.posix.join(directory, "tsconfig.json")))
175
+ continue;
176
+ const manifest = JSON.parse(await readFile2(path2.join(root, file), "utf8"));
177
+ if (hasDependency(manifest, dependency))
178
+ projects.push(path2.dirname(path2.join(root, file)));
179
+ }
180
+ return projects;
181
+ }
182
+
183
+ // plugins/scanners/typescript-project.ts
184
+ async function frameworkSourceFiles(options) {
185
+ const { root, dependency, projects, sources, also } = options;
186
+ const directories = (await frameworkProjects(root, dependency, projects)).map((directory) => path3.relative(root, directory).split(path3.sep).join("/") || ".");
187
+ if (directories.length === 0)
188
+ return [];
189
+ const listed = [];
190
+ for (const file of await projectFiles(root, (candidate) => !candidate.endsWith(".d.ts"))) {
191
+ const project = directories.find((directory) => directory === "." || file.startsWith(`${directory}/`));
192
+ if (project === undefined)
193
+ continue;
194
+ const local = project === "." ? file : file.slice(project.length + 1);
195
+ if (sources.some((extension) => file.endsWith(extension)) || (also?.(local) ?? false))
196
+ listed.push(file);
197
+ }
198
+ return listed.sort();
199
+ }
200
+
201
+ // plugins/scanners/react/src/routes.ts
202
+ import { existsSync as existsSync2 } from "fs";
203
+ import path4 from "path";
204
+ import ts2 from "./typescript.cjs";
205
+
206
+ // plugins/scanners/http-url.ts
207
+ var pathText = /^[A-Za-z0-9\-._~!$&'()*+,;=:@%]+$/;
208
+ var absolute = /^([A-Za-z][A-Za-z\d+.-]*:)?\/\//;
209
+ var HOLE = "\x00";
210
+ var computedPart = { kind: "hole", configured: false };
211
+ var configuredPart = { kind: "hole", configured: true };
212
+ var methodToken = /^[A-Z][A-Z-]*$/;
213
+ function methodText(text) {
214
+ const method = text?.toUpperCase();
215
+ return method !== undefined && methodToken.test(method) ? method : undefined;
216
+ }
217
+ function requestSegment(part) {
218
+ if (part === HOLE)
219
+ return { kind: "dynamic" };
220
+ if (part.includes(HOLE) || !pathText.test(part))
221
+ return { kind: "unknown" };
222
+ return { kind: "literal", value: part };
223
+ }
224
+ function segmentsOf(parts) {
225
+ const url = parts.map((part) => part.kind === "text" ? part.text : HOLE).join("");
226
+ const end = Math.min(...["?", "#"].map((mark) => url.includes(mark) ? url.indexOf(mark) : url.length));
227
+ return url.slice(0, end).split("/").filter((part) => part !== "").map(requestSegment);
228
+ }
229
+ function afterAuthority(text) {
230
+ const slash = text.indexOf("/", text.indexOf("//") + 2);
231
+ return slash < 0 ? "" : text.slice(slash);
232
+ }
233
+ function joinedText(input) {
234
+ const parts = [];
235
+ for (const part of input) {
236
+ const last = parts.at(-1);
237
+ if (part.kind === "text" && last?.kind === "text")
238
+ parts[parts.length - 1] = { kind: "text", text: last.text + part.text };
239
+ else if (part.kind === "hole" || part.text !== "")
240
+ parts.push(part);
241
+ }
242
+ return parts;
243
+ }
244
+ function requestUrl(input) {
245
+ const parts = joinedText(input);
246
+ const [first, ...rest] = parts;
247
+ if (first === undefined)
248
+ return { path: [{ kind: "unknown" }] };
249
+ if (first.kind === "hole") {
250
+ const path = segmentsOf(rest);
251
+ const next = rest[0];
252
+ const separated = next === undefined || next.kind === "text" && next.text.startsWith("/");
253
+ return first.configured && separated ? { configured: true, path } : { path: [{ kind: "unknown" }, ...path] };
254
+ }
255
+ if (absolute.test(first.text)) {
256
+ return { path: [{ kind: "unknown" }, ...segmentsOf([{ kind: "text", text: afterAuthority(first.text) }, ...rest])] };
257
+ }
258
+ if (!first.text.startsWith("/"))
259
+ return { path: [{ kind: "unknown" }, ...segmentsOf(parts)] };
260
+ return { path: segmentsOf(parts) };
261
+ }
262
+ function joinBase(base, url) {
263
+ const stated = joinedText(base);
264
+ const path = joinedText(url);
265
+ const [first] = path;
266
+ if (stated.length === 0)
267
+ return path;
268
+ if (first === undefined)
269
+ return stated;
270
+ if (first.kind === "hole")
271
+ return [computedPart, ...path.slice(1)];
272
+ return absolute.test(first.text) ? path : [...stated, { kind: "text", text: "/" }, ...path];
273
+ }
274
+ function withBase(base, url) {
275
+ const stated = joinedText(base);
276
+ const path = joinedText(url);
277
+ const [only] = stated;
278
+ const [first, ...rest] = path;
279
+ if (stated.length !== 1 || only?.kind !== "text" || first?.kind !== "text" || absolute.test(first.text)) {
280
+ return joinBase(stated, path);
281
+ }
282
+ const prefix = only.text.replace(/\/$/, "");
283
+ if (first.text.startsWith(prefix)) {
284
+ const next = first.text[prefix.length];
285
+ if (next === undefined)
286
+ return rest.length === 0 ? path : [computedPart, ...rest];
287
+ return next === "/" || next === "?" ? path : joinBase(stated, path);
288
+ }
289
+ return prefix.startsWith(first.text) && rest.length > 0 ? [computedPart, ...rest] : joinBase(stated, path);
290
+ }
291
+
292
+ // plugins/scanners/react/src/functions.ts
293
+ import ts from "./typescript.cjs";
294
+ function executable(node) {
295
+ return ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) && !!node.body;
296
+ }
297
+
298
+ // plugins/scanners/react/src/routes.ts
299
+ var METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
300
+ var APP_ROUTE = /^(?:(.+)\/)?route\.tsx?$/;
301
+ var PAGES_API = /^(api\/.+)\.tsx?$/;
302
+ var OPTIONAL_CATCH_ALL = /^\[\[\.\.\.(.+)\]\]$/;
303
+ var CATCH_ALL = /^\[\.\.\.(.+)\]$/;
304
+ var PARAMETER = /^\[(.+)\]$/;
305
+ function nextRouters(projectRoot) {
306
+ const active = (name) => existsSync2(path4.join(projectRoot, name)) ? name : `src/${name}`;
307
+ return { app: active("app"), pages: active("pages") };
308
+ }
309
+ function routeLocation(file, routers) {
310
+ const within = (directory) => file.startsWith(`${directory}/`) ? file.slice(directory.length + 1) : undefined;
311
+ const app = APP_ROUTE.exec(within(routers.app) ?? "");
312
+ if (app)
313
+ return { router: "app", ...app[1] === undefined ? {} : { directory: app[1] } };
314
+ const pages = PAGES_API.exec(within(routers.pages) ?? "");
315
+ return pages ? { router: "pages", directory: pages[1] } : undefined;
316
+ }
317
+ function routeCandidate(file) {
318
+ return ["", "src/"].some((prefix) => routeLocation(file, { app: `${prefix}app`, pages: `${prefix}pages` }) !== undefined);
319
+ }
320
+ var GROUP = /^\((?!\.+\))[^)]+\)$/;
321
+ function routeSegment(part, last) {
322
+ const optional = OPTIONAL_CATCH_ALL.exec(part);
323
+ if (optional)
324
+ return last && pathText.test(optional[1]) ? { kind: "catch-all", name: optional[1], optional: true } : undefined;
325
+ const rest = CATCH_ALL.exec(part);
326
+ if (rest)
327
+ return last && pathText.test(rest[1]) ? { kind: "catch-all", name: rest[1] } : undefined;
328
+ const parameter = PARAMETER.exec(part);
329
+ if (parameter)
330
+ return pathText.test(parameter[1]) ? { kind: "parameter", name: parameter[1] } : undefined;
331
+ if (part.startsWith("(") || part.startsWith("@") || part.includes("[") || part.includes("]"))
332
+ return;
333
+ return pathText.test(part) ? { kind: "literal", value: part } : undefined;
334
+ }
335
+ function routeParts(directory, stripIndex) {
336
+ const parts = (directory ?? "").split("/").filter((part) => part !== "" && !GROUP.test(part));
337
+ return stripIndex && parts.at(-1) === "index" ? parts.slice(0, -1) : parts;
338
+ }
339
+ function routePath(directory, stripIndex) {
340
+ const parts = routeParts(directory, stripIndex);
341
+ const segments = [];
342
+ for (const [index, part] of parts.entries()) {
343
+ const segment = routeSegment(part, index === parts.length - 1);
344
+ if (segment === undefined)
345
+ return;
346
+ segments.push(segment);
347
+ }
348
+ return segments;
349
+ }
350
+ function functionValue(node) {
351
+ return node !== undefined && executable(node) ? node : undefined;
352
+ }
353
+ function hasModifier2(statement, kind) {
354
+ return ts2.canHaveModifiers(statement) && (ts2.getModifiers(statement) ?? []).some((modifier) => modifier.kind === kind);
355
+ }
356
+ function declaredHandler(statement) {
357
+ if (!ts2.isFunctionDeclaration(statement) || statement.name === undefined)
358
+ return;
359
+ const handler = functionValue(statement);
360
+ return handler !== undefined && METHODS.has(statement.name.text) ? [statement.name.text, handler] : undefined;
361
+ }
362
+ function boundHandlers(statement) {
363
+ if (!ts2.isVariableStatement(statement))
364
+ return [];
365
+ return statement.declarationList.declarations.flatMap((declaration) => {
366
+ const handler = functionValue(declaration.initializer);
367
+ if (handler === undefined || !ts2.isIdentifier(declaration.name) || !METHODS.has(declaration.name.text))
368
+ return [];
369
+ return [[declaration.name.text, handler]];
370
+ });
371
+ }
372
+ function appHandlers(source) {
373
+ const exported = source.statements.filter((statement) => hasModifier2(statement, ts2.SyntaxKind.ExportKeyword));
374
+ return new Map(exported.flatMap((statement) => {
375
+ const declared = declaredHandler(statement);
376
+ return declared === undefined ? boundHandlers(statement) : [declared];
377
+ }));
378
+ }
379
+ function localFunction(source, name) {
380
+ for (const statement of source.statements) {
381
+ if (ts2.isFunctionDeclaration(statement) && statement.name?.text === name)
382
+ return functionValue(statement);
383
+ if (!ts2.isVariableStatement(statement))
384
+ continue;
385
+ for (const declaration of statement.declarationList.declarations) {
386
+ if (ts2.isIdentifier(declaration.name) && declaration.name.text === name)
387
+ return functionValue(declaration.initializer);
388
+ }
389
+ }
390
+ return;
391
+ }
392
+ function pagesHandler(source) {
393
+ for (const statement of source.statements) {
394
+ if (ts2.isFunctionDeclaration(statement) && hasModifier2(statement, ts2.SyntaxKind.DefaultKeyword))
395
+ return functionValue(statement);
396
+ if (!ts2.isExportAssignment(statement) || statement.isExportEquals === true)
397
+ continue;
398
+ const value = functionValue(statement.expression);
399
+ if (value !== undefined)
400
+ return value;
401
+ if (ts2.isIdentifier(statement.expression))
402
+ return localFunction(source, statement.expression.text);
403
+ }
404
+ return;
405
+ }
406
+ function nextRouteEndpoints(sources, projectRoot, routers, operationId) {
407
+ const endpoints = [];
408
+ for (const source of sources) {
409
+ const location = routeLocation(path4.relative(projectRoot, source.fileName).split(path4.sep).join("/"), routers);
410
+ const segments = location === undefined ? undefined : routePath(location.directory, location.router === "pages");
411
+ if (location === undefined || segments === undefined)
412
+ continue;
413
+ if (location.router === "app") {
414
+ for (const [method, handler] of appHandlers(source)) {
415
+ endpoints.push({ operation: operationId(handler), method, path: segments });
416
+ }
417
+ continue;
418
+ }
419
+ const handler = pagesHandler(source);
420
+ if (handler !== undefined)
421
+ endpoints.push({ operation: operationId(handler), method: "*", path: segments });
422
+ }
423
+ return endpoints;
424
+ }
425
+
426
+ // plugins/scanners/react/src/scan.ts
427
+ import { existsSync as existsSync3, readFileSync } from "fs";
428
+ import path5 from "path";
429
+
430
+ // packages/scanner/src/values.ts
431
+ function object(value, label) {
432
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
433
+ throw new Error(`${label} must be an object`);
434
+ }
435
+ return value;
436
+ }
437
+ function string(value, label) {
438
+ if (typeof value !== "string")
439
+ throw new Error(`${label} must be a string`);
440
+ return value;
441
+ }
442
+ function array(value, label) {
443
+ if (!Array.isArray(value))
444
+ throw new Error(`${label} must be an array`);
445
+ return value;
446
+ }
447
+
448
+ // packages/scanner/src/http.ts
449
+ var pathText2 = /^[A-Za-z0-9\-._~!$&'()*+,;=:@%]+$/;
450
+ var methodToken2 = /^[A-Z][A-Z-]*$/;
451
+ function fail(message) {
452
+ throw new Error(message);
453
+ }
454
+ function text(value, label) {
455
+ if (typeof value !== "string" || !pathText2.test(value))
456
+ fail(`${label} must be nonempty URL path characters`);
457
+ return value;
458
+ }
459
+ function method(value, label, anyMethod) {
460
+ if (typeof value !== "string" || !(methodToken2.test(value) || anyMethod && value === "*")) {
461
+ fail(`${label} must be an uppercase HTTP method${anyMethod ? " or *" : ""}`);
462
+ }
463
+ return value;
464
+ }
465
+ function operation(value, operations) {
466
+ if (typeof value !== "string" || !operations.has(value))
467
+ fail("HTTP fact references unknown operation");
468
+ return value;
469
+ }
470
+ function endpointSegment(value, last) {
471
+ const segment = object(value, "HTTP endpoint segment");
472
+ if (segment.kind === "literal")
473
+ return { kind: "literal", value: text(segment.value, "HTTP literal segment") };
474
+ if (segment.kind !== "parameter" && segment.kind !== "catch-all")
475
+ fail(`unknown HTTP endpoint segment kind: ${String(segment.kind)}`);
476
+ if (segment.kind === "catch-all" && !last)
477
+ fail("an HTTP catch-all segment must be last");
478
+ for (const flag of ["optional", "constrained"]) {
479
+ if (segment[flag] !== undefined && typeof segment[flag] !== "boolean")
480
+ fail(`HTTP segment ${flag} must be a boolean`);
481
+ }
482
+ return {
483
+ kind: segment.kind,
484
+ name: text(segment.name, "HTTP segment name"),
485
+ ...segment.optional ? { optional: true } : {},
486
+ ...segment.constrained ? { constrained: true } : {}
487
+ };
488
+ }
489
+ function requestSegment2(value) {
490
+ const segment = object(value, "HTTP request segment");
491
+ if (segment.kind === "literal")
492
+ return { kind: "literal", value: text(segment.value, "HTTP literal segment") };
493
+ if (segment.kind === "dynamic" || segment.kind === "unknown")
494
+ return { kind: segment.kind };
495
+ return fail(`unknown HTTP request segment kind: ${String(segment.kind)}`);
496
+ }
497
+ function order(value) {
498
+ const fact = object(value, "HTTP endpoint order");
499
+ const application = string(fact.application, "HTTP endpoint order application");
500
+ if (application === "")
501
+ fail("HTTP endpoint order application must be nonempty");
502
+ const position = fact.position;
503
+ if (typeof position !== "number" || !Number.isInteger(position) || position < 0) {
504
+ fail("HTTP endpoint order position must be a nonnegative integer");
505
+ }
506
+ return { application, position };
507
+ }
508
+ function endpoint(value, operations) {
509
+ const fact = object(value, "HTTP endpoint");
510
+ const path = array(fact.path, "HTTP endpoint path");
511
+ return {
512
+ operation: operation(fact.operation, operations),
513
+ method: method(fact.method, "HTTP endpoint method", true),
514
+ path: path.map((segment, index) => endpointSegment(segment, index === path.length - 1)),
515
+ ...fact.order === undefined ? {} : { order: order(fact.order) }
516
+ };
517
+ }
518
+ function request(value, operations) {
519
+ const fact = object(value, "HTTP request");
520
+ if (fact.configured !== undefined && typeof fact.configured !== "boolean")
521
+ fail("HTTP request configured must be a boolean");
522
+ return {
523
+ operation: operation(fact.operation, operations),
524
+ ...fact.method === undefined ? {} : { method: method(fact.method, "HTTP request method", false) },
525
+ ...fact.configured ? { configured: true } : {},
526
+ path: array(fact.path, "HTTP request path").map(requestSegment2)
527
+ };
528
+ }
529
+ function httpEvidence(endpoints, requests, operations) {
530
+ if (endpoints === undefined && requests === undefined)
531
+ return {};
532
+ if (operations === undefined)
533
+ fail("HTTP facts require operation declarations");
534
+ return {
535
+ ...endpoints === undefined ? {} : { httpEndpoints: array(endpoints, "httpEndpoints").map((fact) => endpoint(fact, operations)) },
536
+ ...requests === undefined ? {} : { httpRequests: array(requests, "httpRequests").map((fact) => request(fact, operations)) }
537
+ };
538
+ }
6
539
 
7
540
  // packages/scanner/src/index.ts
8
541
  function compare(...values) {
@@ -47,7 +580,9 @@ function createScanObservation(input) {
47
580
  scanner: input.scanner,
48
581
  roots,
49
582
  files,
583
+ ...sourceUnits(input.sourceUnits, filePaths),
50
584
  ...operationEvidence(input, filePaths),
585
+ ...httpEvidence(input.httpEndpoints, input.httpRequests, input.operations && new Set(input.operations.map((operation) => operation.id))),
51
586
  diagnostics: [...new Map(input.diagnostics.map((diagnostic) => [
52
587
  diagnosticKey(diagnostic),
53
588
  diagnostic
@@ -56,6 +591,23 @@ function createScanObservation(input) {
56
591
  })
57
592
  };
58
593
  }
594
+ function sourceUnits(input, paths) {
595
+ if (input === undefined)
596
+ return {};
597
+ const units = array(input, "sourceUnits").map((entry) => {
598
+ const unit = object(entry, "source unit");
599
+ const primary = string(unit.primary, "source unit.primary");
600
+ const files = [...new Set(array(unit.files, "source unit.files").map((file) => string(file, "source unit.file")))].sort();
601
+ if (!files.includes(primary))
602
+ throw new Error(`source unit omits primary file: ${primary}`);
603
+ for (const file of files) {
604
+ if (!paths.has(file))
605
+ throw new Error(`source unit references unknown file: ${file}`);
606
+ }
607
+ return { primary, files };
608
+ });
609
+ return { sourceUnits: [...new Map(units.map((unit) => [compare(unit.primary, ...unit.files), unit])).values()].sort((a, b) => compare(a.primary, ...a.files).localeCompare(compare(b.primary, ...b.files))) };
610
+ }
59
611
  function validateRoots(input) {
60
612
  const roots = uniqueBy(input, (root) => root.id, "root id");
61
613
  const byId = new Map(roots.map((root) => [root.id, root]));
@@ -86,16 +638,6 @@ function diagnosticLocation(value) {
86
638
  ...value.line === undefined ? {} : { line: Number(value.line) }
87
639
  };
88
640
  }
89
- function string(value, label) {
90
- if (typeof value !== "string")
91
- throw new Error(`${label} must be a string`);
92
- return value;
93
- }
94
- function array(value, label) {
95
- if (!Array.isArray(value))
96
- throw new Error(`${label} must be an array`);
97
- return value;
98
- }
99
641
  function operationEvidence(input, files) {
100
642
  if (input.operations === undefined && input.invocations !== undefined)
101
643
  throw new Error("invocations require operation declarations");
@@ -167,47 +709,7 @@ function sourcePosition(position) {
167
709
  }
168
710
 
169
711
  // plugins/scanners/react/src/scan.ts
170
- import ts from "./typescript.cjs";
171
-
172
- // plugins/scanners/projects.ts
173
- import { execFile } from "child_process";
174
- import { existsSync } from "fs";
175
- import { readFile } from "fs/promises";
176
- import path from "path";
177
- import { promisify } from "util";
178
- var execute = promisify(execFile);
179
- var excluded = new Set([
180
- ".git",
181
- "node_modules",
182
- "vendor",
183
- "target",
184
- "dist",
185
- "build",
186
- "bin",
187
- "obj",
188
- ".gradle",
189
- ".angular",
190
- "coverage",
191
- "generated",
192
- "groma",
193
- ".groma"
194
- ]);
195
- async function projectFiles(root, matches) {
196
- const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
197
- return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && !file.split("/").some((part) => excluded.has(part)) && existsSync(path.join(root, file))))].sort();
198
- }
199
- function hasDependency(manifest, dependency) {
200
- return ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"].some((section) => typeof manifest[section]?.[dependency] === "string");
201
- }
202
- async function frameworkProjects(root, dependency) {
203
- const projects = [];
204
- for (const file of await projectFiles(root, (file) => path.posix.basename(file) === "package.json")) {
205
- const manifest = JSON.parse(await readFile(path.join(root, file), "utf8"));
206
- if (hasDependency(manifest, dependency))
207
- projects.push(path.dirname(path.join(root, file)));
208
- }
209
- return projects;
210
- }
712
+ import ts4 from "./typescript.cjs";
211
713
 
212
714
  // plugins/scanners/observations.ts
213
715
  function combineObservations(parts) {
@@ -218,6 +720,9 @@ function combineObservations(parts) {
218
720
  const operations = [];
219
721
  const invocations = [];
220
722
  const diagnostics = [];
723
+ const sourceUnits = [];
724
+ const httpEndpoints = [];
725
+ const httpRequests = [];
221
726
  for (const { key, observation } of parts) {
222
727
  const id = (value) => JSON.stringify([key, value]);
223
728
  roots.push(...observation.roots.map((root) => ({ ...root, id: id(root.id), ...root.parent ? { parent: id(root.parent) } : {} })));
@@ -229,7 +734,10 @@ function combineObservations(parts) {
229
734
  }
230
735
  operations.push(...(observation.operations ?? []).map((operation) => ({ ...operation, id: id(operation.id) })));
231
736
  invocations.push(...(observation.invocations ?? []).map((call) => ({ ...call, source: id(call.source), targets: call.targets.map(id) })));
737
+ httpEndpoints.push(...(observation.httpEndpoints ?? []).map((endpoint) => ({ ...endpoint, operation: id(endpoint.operation) })));
738
+ httpRequests.push(...(observation.httpRequests ?? []).map((request) => ({ ...request, operation: id(request.operation) })));
232
739
  diagnostics.push(...observation.diagnostics);
740
+ sourceUnits.push(...observation.sourceUnits ?? []);
233
741
  }
234
742
  return createScanObservation({
235
743
  scanner: parts[0].observation.scanner,
@@ -237,58 +745,810 @@ function combineObservations(parts) {
237
745
  files: [...files.values()],
238
746
  operations,
239
747
  invocations,
240
- diagnostics
748
+ diagnostics,
749
+ ...parts.some((part) => part.observation.sourceUnits !== undefined) ? { sourceUnits } : {},
750
+ ...parts.some((part) => part.observation.httpEndpoints !== undefined) ? { httpEndpoints } : {},
751
+ ...parts.some((part) => part.observation.httpRequests !== undefined) ? { httpRequests } : {}
241
752
  });
242
753
  }
243
754
 
755
+ // plugins/scanners/react/src/http.ts
756
+ import ts3 from "./typescript.cjs";
757
+
758
+ // plugins/scanners/http-syntax.ts
759
+ function wrapper(ts, node) {
760
+ return ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isNonNullExpression(node) || ts.isTypeAssertionExpression(node);
761
+ }
762
+ function unwrapped(ts, node) {
763
+ let current = node;
764
+ while (wrapper(ts, current))
765
+ current = current.expression;
766
+ return current;
767
+ }
768
+ function propertyKey(ts, property) {
769
+ const name = property.name;
770
+ if (name === undefined)
771
+ return;
772
+ return ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name) ? name.text : undefined;
773
+ }
774
+ function ownProperty(ts, object, name) {
775
+ let found = "absent";
776
+ for (const property of object.properties) {
777
+ const key = propertyKey(ts, property);
778
+ if (key === undefined || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property))
779
+ return "unknown";
780
+ if (key === name)
781
+ found = ts.isPropertyAssignment(property) ? { node: property.initializer } : "unknown";
782
+ }
783
+ return found;
784
+ }
785
+ function holderOf(ts, node) {
786
+ let current = node;
787
+ while (wrapper(ts, current.parent))
788
+ current = current.parent;
789
+ return ts.isVariableDeclaration(current.parent) ? current.parent : undefined;
790
+ }
791
+ function urlText(ts, node) {
792
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node))
793
+ return true;
794
+ return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken;
795
+ }
796
+
797
+ // plugins/scanners/http-uses.ts
798
+ function patternPart(ts, parent, target) {
799
+ if (ts.isPropertyAssignment(parent))
800
+ return parent.initializer === target;
801
+ return ts.isArrayLiteralExpression(parent) || ts.isObjectLiteralExpression(parent) || ts.isSpreadElement(parent) || ts.isSpreadAssignment(parent) || ts.isShorthandPropertyAssignment(parent) || wrapper(ts, parent);
802
+ }
803
+ function assignment(ts, parent, direct) {
804
+ const operator = parent.operatorToken.kind;
805
+ if (operator < ts.SyntaxKind.FirstAssignment || operator > ts.SyntaxKind.LastAssignment)
806
+ return;
807
+ return operator === ts.SyntaxKind.EqualsToken && direct ? { assigned: parent.right } : {};
808
+ }
809
+ function counted(ts, parent) {
810
+ return (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) && (parent.operator === ts.SyntaxKind.PlusPlusToken || parent.operator === ts.SyntaxKind.MinusMinusToken);
811
+ }
812
+ function writeOf(ts, node) {
813
+ let target = node;
814
+ while (patternPart(ts, target.parent, target))
815
+ target = target.parent;
816
+ const parent = target.parent;
817
+ const direct = target === node || wrapper(ts, target);
818
+ if (ts.isBinaryExpression(parent) && parent.left === target)
819
+ return assignment(ts, parent, direct);
820
+ if ((ts.isForInStatement(parent) || ts.isForOfStatement(parent)) && parent.initializer === target)
821
+ return {};
822
+ return direct && (counted(ts, parent) || ts.isDeleteExpression(parent)) ? {} : undefined;
823
+ }
824
+ function literalKey(ts, node) {
825
+ return ts.isStringLiteral(node) || ts.isNumericLiteral(node) ? node.text : undefined;
826
+ }
827
+ function calledThrough(ts, node) {
828
+ const parent = node.parent;
829
+ if (ts.isCallExpression(parent) || ts.isNewExpression(parent))
830
+ return parent.expression === node;
831
+ return ts.isTaggedTemplateExpression(parent) && parent.tag === node;
832
+ }
833
+ function accessKey(ts, parent, object) {
834
+ if (ts.isPropertyAccessExpression(parent)) {
835
+ return parent.expression === object && ts.isIdentifier(parent.name) ? parent.name.text : undefined;
836
+ }
837
+ if (!ts.isElementAccessExpression(parent) || parent.expression !== object)
838
+ return;
839
+ return literalKey(ts, parent.argumentExpression);
840
+ }
841
+ function useOf(ts, reference) {
842
+ let chain = reference;
843
+ let receiver;
844
+ const path = [];
845
+ for (;; ) {
846
+ const parent = chain.parent;
847
+ if (wrapper(ts, parent)) {
848
+ chain = parent;
849
+ continue;
850
+ }
851
+ const key = accessKey(ts, parent, chain);
852
+ if (key === undefined)
853
+ break;
854
+ receiver = chain;
855
+ path.push(key);
856
+ chain = parent;
857
+ }
858
+ const write = writeOf(ts, chain);
859
+ if (write !== undefined)
860
+ return { kind: "write", path, reference, value: chain, ...write };
861
+ if (receiver !== undefined && calledThrough(ts, chain)) {
862
+ return { kind: path.length > 1 ? "read" : "escape", path: path.slice(0, -1), reference, value: receiver };
863
+ }
864
+ return { kind: path.length > 0 ? "read" : "escape", path, reference, value: chain };
865
+ }
866
+ function importedName(ts, node) {
867
+ const parent = node.parent;
868
+ if (ts.isImportClause(parent))
869
+ return parent.name === node;
870
+ return (ts.isImportSpecifier(parent) || ts.isNamespaceImport(parent) || ts.isImportEqualsDeclaration(parent)) && parent.name === node;
871
+ }
872
+ function typePosition(ts, node) {
873
+ let current = node;
874
+ while (ts.isQualifiedName(current.parent))
875
+ current = current.parent;
876
+ return ts.isTypeQueryNode(current.parent);
877
+ }
878
+ function exportStatement(ts, node) {
879
+ const parent = node.parent;
880
+ return ts.isExportSpecifier(parent) || ts.isExportAssignment(parent) && parent.expression === node;
881
+ }
882
+ function exported(ts, declaration) {
883
+ const statement = declaration.parent.parent;
884
+ return statement?.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) === true;
885
+ }
886
+ function topLevel(ts, declaration) {
887
+ const statement = declaration.parent?.parent;
888
+ return statement?.parent !== undefined && ts.isSourceFile(statement.parent);
889
+ }
890
+ function scriptFile(ts, source, names) {
891
+ if (ts.isExternalModule(source) || /\.[cm]js$/.test(source.fileName))
892
+ return false;
893
+ const required = (names.get("require") ?? []).some((node) => ts.isCallExpression(node.parent) && node.parent.expression === node);
894
+ const exporting = ["module", "exports"].some((name) => (names.get(name) ?? []).some((node) => ts.isPropertyAccessExpression(node.parent) && node.parent.expression === node));
895
+ return !required && !exporting;
896
+ }
897
+ function dynamicBinding(ts, call) {
898
+ let holder = call;
899
+ let awaited = false;
900
+ while (ts.isAwaitExpression(holder.parent) || ts.isParenthesizedExpression(holder.parent)) {
901
+ awaited ||= ts.isAwaitExpression(holder.parent);
902
+ holder = holder.parent;
903
+ }
904
+ const parent = holder.parent;
905
+ if (ts.isVariableDeclaration(parent))
906
+ return awaited && ts.isIdentifier(parent.name) ? parent.name : undefined;
907
+ const then = ts.isPropertyAccessExpression(parent) && parent.expression === holder && !awaited ? parent.parent : undefined;
908
+ const callback = then !== undefined && ts.isCallExpression(then) ? then.arguments[0] : undefined;
909
+ if (callback === undefined || !ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback))
910
+ return;
911
+ const [first] = callback.parameters;
912
+ return first !== undefined && ts.isIdentifier(first.name) ? first.name : undefined;
913
+ }
914
+ function requiredModule(ts, node) {
915
+ if (node !== undefined && ts.isExternalModuleReference(node))
916
+ return ts.isStringLiteral(node.expression) ? node.expression.text : undefined;
917
+ if (node === undefined || !ts.isCallExpression(node) || !ts.isIdentifier(node.expression) || node.expression.text !== "require")
918
+ return;
919
+ const [specifier] = node.arguments;
920
+ return specifier !== undefined && ts.isStringLiteral(specifier) ? specifier.text : undefined;
921
+ }
922
+ function importModule(ts, statement, name) {
923
+ return ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) ? { module: statement.moduleSpecifier.text, name } : undefined;
924
+ }
925
+ function requiredValue(ts, initializer) {
926
+ const value = initializer === undefined ? undefined : unwrapped(ts, initializer);
927
+ const direct = requiredModule(ts, value);
928
+ if (direct !== undefined)
929
+ return { module: direct, name: "default" };
930
+ if (value === undefined || !ts.isPropertyAccessExpression(value) || !ts.isIdentifier(value.name))
931
+ return;
932
+ const module = requiredModule(ts, unwrapped(ts, value.expression));
933
+ return module === undefined ? undefined : { module, name: value.name.text };
934
+ }
935
+ function boundExport(ts, declaration) {
936
+ if (ts.isImportClause(declaration))
937
+ return importModule(ts, declaration.parent, "default");
938
+ if (ts.isNamespaceImport(declaration))
939
+ return importModule(ts, declaration.parent.parent, "*");
940
+ if (ts.isImportSpecifier(declaration)) {
941
+ return importModule(ts, declaration.parent.parent.parent, (declaration.propertyName ?? declaration.name).text);
942
+ }
943
+ if (ts.isImportEqualsDeclaration(declaration))
944
+ return requiredValue(ts, declaration.moduleReference);
945
+ if (ts.isVariableDeclaration(declaration))
946
+ return requiredValue(ts, declaration.initializer);
947
+ if (!ts.isBindingElement(declaration))
948
+ return;
949
+ const variable = declaration.parent.parent;
950
+ const module = ts.isVariableDeclaration(variable) ? requiredModule(ts, variable.initializer) : undefined;
951
+ const key = declaration.propertyName ?? declaration.name;
952
+ return module !== undefined && key !== undefined && ts.isIdentifier(key) ? { module, name: key.text } : undefined;
953
+ }
954
+ function defaultImport(ts, name) {
955
+ const declaration = name.parent;
956
+ const origin = declaration.name === name ? boundExport(ts, declaration) : undefined;
957
+ return origin?.name === "default" ? { declaration, module: origin.module } : undefined;
958
+ }
959
+ function indexName(ts, index, node) {
960
+ const same = index.names.get(node.text);
961
+ if (same === undefined)
962
+ index.names.set(node.text, [node]);
963
+ else
964
+ same.push(node);
965
+ if (importedName(ts, node))
966
+ index.imported.add(node.text);
967
+ const imported = defaultImport(ts, node);
968
+ if (imported !== undefined)
969
+ index.defaults.set(imported.module, [...index.defaults.get(imported.module) ?? [], imported.declaration]);
970
+ }
971
+ function indexSources(ts, sources) {
972
+ const index = { names: new Map, imported: new Set, dynamic: [], defaults: new Map };
973
+ const visit = (node) => {
974
+ if (ts.isIdentifier(node))
975
+ indexName(ts, index, node);
976
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
977
+ const binding = dynamicBinding(ts, node);
978
+ index.dynamic.push({ call: node, ...binding === undefined ? {} : { binding } });
979
+ }
980
+ ts.forEachChild(node, visit);
981
+ };
982
+ for (const source of sources)
983
+ visit(source);
984
+ return index;
985
+ }
986
+ function startsWith(path, prefix) {
987
+ return prefix.length <= path.length && prefix.every((name, index) => path[index] === name);
988
+ }
989
+ function useAt(ts, node) {
990
+ const parent = node.parent;
991
+ if (ts.isVariableDeclaration(parent) && parent.name === node)
992
+ return { kind: "write", path: [], reference: node, value: node };
993
+ if (exportStatement(ts, node))
994
+ return { kind: "export", path: [], reference: node, value: node };
995
+ const accessed = ts.isPropertyAccessExpression(parent) && parent.name === node;
996
+ return useOf(ts, accessed ? parent : node);
997
+ }
998
+ function objectUse(ts, node) {
999
+ const parent = node.parent;
1000
+ const named = ts.isPropertyAccessExpression(parent) && parent.expression === node && ts.isIdentifier(parent.name);
1001
+ const declaring = (ts.isVariableDeclaration(parent) || ts.isParameter(parent)) && parent.name === node;
1002
+ return !named && !declaring && !importedName(ts, node) && !typePosition(ts, node);
1003
+ }
1004
+ function settingEffect(use, path, keys) {
1005
+ if (use.path.length === 0 || use.kind === "export")
1006
+ return { kind: "none" };
1007
+ if (startsWith(path, use.path))
1008
+ return { kind: "changes" };
1009
+ const key = startsWith(use.path, path) ? use.path[path.length] : undefined;
1010
+ if (key === undefined || !keys.includes(key))
1011
+ return { kind: "none" };
1012
+ if (use.assigned !== undefined && use.path.length === path.length + 1)
1013
+ return { kind: "assigns", key, value: use.assigned };
1014
+ return { kind: "changes" };
1015
+ }
1016
+
1017
+ // plugins/scanners/http-bindings.ts
1018
+ function bindingUses(ts, checker, sources, options) {
1019
+ let index;
1020
+ const indexed = () => {
1021
+ index ??= indexSources(ts, sources);
1022
+ return index;
1023
+ };
1024
+ const uses = new Map;
1025
+ const symbols = new Map;
1026
+ const shorthand = (node) => ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node;
1027
+ const resolveAll = (names) => {
1028
+ const pending = names.filter((node) => !symbols.has(node) && !shorthand(node));
1029
+ const batch = pending.length === 0 ? Promise.resolve([]) : checker.symbolsAt(pending);
1030
+ for (const [position, node] of pending.entries())
1031
+ symbols.set(node, batch.then((found) => found[position]));
1032
+ };
1033
+ const symbolOf = (node) => {
1034
+ let found = symbols.get(node);
1035
+ if (found === undefined) {
1036
+ found = shorthand(node) ? checker.shorthandValue(node.parent) : checker.symbolsAt([node]).then(([symbol]) => symbol);
1037
+ symbols.set(node, found);
1038
+ }
1039
+ return found;
1040
+ };
1041
+ const refersTo = async (node, declaration) => {
1042
+ const symbol = await symbolOf(node);
1043
+ if (symbol === undefined || node.parent === declaration)
1044
+ return false;
1045
+ if (await checker.declaration(symbol) === declaration)
1046
+ return true;
1047
+ if (importedName(ts, node))
1048
+ return false;
1049
+ return await checker.declaration(await checker.aliased(symbol)) === declaration;
1050
+ };
1051
+ const usesOf = (declaration) => {
1052
+ let found = uses.get(declaration);
1053
+ if (found !== undefined)
1054
+ return found;
1055
+ found = (async () => {
1056
+ const { names, imported } = indexed();
1057
+ const name = declaration.name !== undefined && ts.isIdentifier(declaration.name) ? declaration.name.text : undefined;
1058
+ const texts = name === undefined ? [] : [name, "default", ...imported];
1059
+ const candidates = [...new Set(texts.flatMap((text) => names.get(text) ?? []))].filter((node) => !typePosition(ts, node));
1060
+ resolveAll(candidates);
1061
+ const referring = await Promise.all(candidates.map((node) => refersTo(node, declaration)));
1062
+ return candidates.filter((_, position) => referring[position]).map((node) => useAt(ts, node));
1063
+ })();
1064
+ uses.set(declaration, found);
1065
+ return found;
1066
+ };
1067
+ const dynamicEscapes = async ({ binding }) => {
1068
+ if (binding === undefined)
1069
+ return true;
1070
+ const bound = await symbolOf(binding);
1071
+ const references = indexed().names.get(binding.text) ?? [];
1072
+ resolveAll(references);
1073
+ for (const node of references) {
1074
+ const symbol = await symbolOf(node);
1075
+ if (bound !== undefined && symbol !== undefined && checker.same(symbol, bound) && objectUse(ts, node))
1076
+ return true;
1077
+ }
1078
+ return false;
1079
+ };
1080
+ const moduleOf = async (node) => {
1081
+ const symbol = await symbolOf(node);
1082
+ const module = symbol === undefined ? undefined : await checker.aliased(symbol);
1083
+ return module !== undefined && checker.module(module) ? module : undefined;
1084
+ };
1085
+ const loadedModule = async (entry) => {
1086
+ const [argument] = entry.call.arguments;
1087
+ const [module] = argument === undefined ? [] : await checker.symbolsAt([argument]);
1088
+ return module !== undefined && await dynamicEscapes(entry) ? module : undefined;
1089
+ };
1090
+ const escapedModules = async () => {
1091
+ const { names, imported, dynamic } = indexed();
1092
+ const importedNames = [...imported].flatMap((text) => names.get(text) ?? []).filter((node) => objectUse(ts, node));
1093
+ resolveAll(importedNames);
1094
+ const found = await Promise.all([...importedNames.map(moduleOf), ...dynamic.map(loadedModule)]);
1095
+ const modules = [];
1096
+ for (const module of found) {
1097
+ if (module !== undefined && !modules.some((known) => checker.same(known, module)))
1098
+ modules.push(module);
1099
+ }
1100
+ return modules;
1101
+ };
1102
+ let reachable;
1103
+ const escapedExports = () => {
1104
+ reachable ??= (async () => {
1105
+ const exports = (await Promise.all((await escapedModules()).map((module) => checker.exportsOf(module)))).flat();
1106
+ const declarations = await Promise.all(exports.map(async (symbol) => checker.declaration(await checker.aliased(symbol))));
1107
+ return new Set(declarations.filter((declaration) => declaration !== undefined));
1108
+ })();
1109
+ return reachable;
1110
+ };
1111
+ const script = () => options.fileAlone && sources.some((source) => scriptFile(ts, source, indexed().names));
1112
+ const leaves = async (declaration) => {
1113
+ if (!options.fileAlone)
1114
+ return (await escapedExports()).has(declaration);
1115
+ return exported(ts, declaration) || topLevel(ts, declaration) && script();
1116
+ };
1117
+ const readChanges = async (use) => use.kind !== "read" || !await checker.primitiveAt(use.value);
1118
+ const changes = async (use, path, reference) => {
1119
+ if (use.reference === reference || !startsWith(path, use.path))
1120
+ return false;
1121
+ return use.kind === "export" ? options.fileAlone : readChanges(use);
1122
+ };
1123
+ const settingAt = async (settings, use, path, keys) => {
1124
+ const effect = settingEffect(use, path, keys);
1125
+ if (effect.kind === "assigns")
1126
+ settings.assigned.set(effect.key, [...settings.assigned.get(effect.key) ?? [], effect.value]);
1127
+ else if (effect.kind === "changes")
1128
+ settings.changed ||= await readChanges(use);
1129
+ };
1130
+ return {
1131
+ async unchanged(declaration, path, reference) {
1132
+ if (await leaves(declaration))
1133
+ return false;
1134
+ for (const use of await usesOf(declaration))
1135
+ if (await changes(use, path, reference))
1136
+ return false;
1137
+ return true;
1138
+ },
1139
+ async reassigned(declaration) {
1140
+ if (options.fileAlone && topLevel(ts, declaration) && script())
1141
+ return true;
1142
+ return (await usesOf(declaration)).some((use) => use.kind === "write" && use.path.length === 0);
1143
+ },
1144
+ async settings(declaration, path, keys) {
1145
+ const settings = { assigned: new Map, changed: await leaves(declaration) };
1146
+ for (const use of await usesOf(declaration))
1147
+ await settingAt(settings, use, path, keys);
1148
+ return settings;
1149
+ },
1150
+ defaultImports: (module) => indexed().defaults.get(module) ?? [],
1151
+ references: async (declaration) => (await usesOf(declaration)).map((use) => use.reference),
1152
+ named: (text) => indexed().names.get(text) ?? [],
1153
+ fileAlone: options.fileAlone
1154
+ };
1155
+ }
1156
+
1157
+ // plugins/scanners/http-values.ts
1158
+ function urlContext(ts, checker, sources, fileAlone = false) {
1159
+ return { ts, checker, bindings: bindingUses(ts, checker, sources, { fileAlone }) };
1160
+ }
1161
+ var MAX_DEPTH = 8;
1162
+ function declarationFile(node) {
1163
+ return /\.d\.[cm]?ts$/.test(node.getSourceFile().fileName);
1164
+ }
1165
+ async function declarationOf(context, node) {
1166
+ const { checker } = context;
1167
+ const [symbol] = await checker.symbolsAt([node]);
1168
+ return symbol === undefined ? undefined : checker.declaration(await checker.aliased(symbol));
1169
+ }
1170
+ var moduleNamesByFile = new WeakMap;
1171
+ function moduleNames(ts, source) {
1172
+ let names = moduleNamesByFile.get(source);
1173
+ if (names !== undefined)
1174
+ return names;
1175
+ const found = new Set;
1176
+ const visit = (node) => {
1177
+ const name = node.name;
1178
+ if (name !== undefined && ts.isIdentifier(name) && boundExport(ts, node) !== undefined)
1179
+ found.add(name.text);
1180
+ ts.forEachChild(node, visit);
1181
+ };
1182
+ visit(source);
1183
+ names = found;
1184
+ moduleNamesByFile.set(source, names);
1185
+ return names;
1186
+ }
1187
+ async function importOrigin(context, node) {
1188
+ const { ts, checker } = context;
1189
+ const required = requiredModule(ts, node);
1190
+ if (required !== undefined)
1191
+ return { module: required, name: "default" };
1192
+ if (!ts.isIdentifier(node) || !moduleNames(ts, node.getSourceFile()).has(node.text))
1193
+ return;
1194
+ const [symbol] = await checker.symbolsAt([node]);
1195
+ const declaration = symbol === undefined ? undefined : await checker.declaration(symbol);
1196
+ const origin = declaration === undefined ? undefined : boundExport(ts, declaration);
1197
+ if (origin === undefined || !ts.isVariableDeclaration(declaration) && !ts.isBindingElement(declaration))
1198
+ return origin;
1199
+ return await unassigned(context, declaration) ? origin : undefined;
1200
+ }
1201
+ async function runtimeGlobal(context, node) {
1202
+ if (await importOrigin(context, node) !== undefined)
1203
+ return false;
1204
+ const declaration = await declarationOf(context, node);
1205
+ return declaration === undefined || declarationFile(declaration);
1206
+ }
1207
+ function ambient(ts, declaration) {
1208
+ const flag = ts.NodeFlags.Ambient ?? 0;
1209
+ return ((declaration.flags ?? 0) & flag) !== 0;
1210
+ }
1211
+ async function unassigned(context, declaration) {
1212
+ const { ts, checker } = context;
1213
+ const list = ts.isVariableDeclaration(declaration) ? declaration.parent : declaration.parent.parent.parent;
1214
+ if (((list.flags ?? 0) & ts.NodeFlags.Const) !== 0)
1215
+ return true;
1216
+ if (declaration.name === undefined)
1217
+ return false;
1218
+ const [symbol] = await checker.symbolsAt([declaration.name]);
1219
+ return symbol !== undefined && checker.declarationCount(symbol) === 1 && !await context.bindings.reassigned(declaration);
1220
+ }
1221
+ async function variableHeld(context, name, path, depth) {
1222
+ const { ts } = context;
1223
+ const declaration = await declarationOf(context, name);
1224
+ if (declaration === undefined || declarationFile(declaration) || ambient(ts, declaration))
1225
+ return "unseen";
1226
+ if (path.length === 0 && ts.isFunctionDeclaration(declaration))
1227
+ return { node: declaration };
1228
+ if (!ts.isVariableDeclaration(declaration) || declaration.initializer === undefined)
1229
+ return "unknown";
1230
+ if (!ts.isIdentifier(declaration.name) || !await unassigned(context, declaration))
1231
+ return "unknown";
1232
+ if (path.length > 0 && !await context.bindings.unchanged(declaration, path, name))
1233
+ return "unknown";
1234
+ return held(context, declaration.initializer, path, depth + 1);
1235
+ }
1236
+ async function extendsClass(context, klass, base, depth = 0) {
1237
+ const { ts } = context;
1238
+ if (!ts.isClassLike(klass) || depth > MAX_DEPTH)
1239
+ return false;
1240
+ const clauses = klass.heritageClauses ?? [];
1241
+ const clause = Array.from(clauses).find((entry) => entry.token === ts.SyntaxKind.ExtendsKeyword);
1242
+ const parent = clause?.types[0];
1243
+ const declaration = parent === undefined ? undefined : await declarationOf(context, parent.expression);
1244
+ return declaration !== undefined && (declaration === base || extendsClass(context, declaration, base, depth + 1));
1245
+ }
1246
+ async function assignmentAt(context, node, same, declaration) {
1247
+ const { ts } = context;
1248
+ const parent = node.parent;
1249
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
1250
+ const use = same ? useAt(ts, node) : undefined;
1251
+ return use?.kind === "write" ? { value: use.path.length === 0 ? use.assigned : undefined } : undefined;
1252
+ }
1253
+ if (!ts.isPropertyDeclaration(parent) || parent.name !== node || declaration === undefined || parent === declaration)
1254
+ return;
1255
+ return await extendsClass(context, parent.parent, declaration.parent) ? { value: parent.initializer } : undefined;
1256
+ }
1257
+ async function fieldAssignments(context, name, declaration) {
1258
+ const { ts, checker } = context;
1259
+ const [symbol] = await checker.symbolsAt([name]);
1260
+ const initialized = declaration !== undefined && (ts.isPropertyDeclaration(declaration) || ts.isPropertyAssignment(declaration)) ? declaration.initializer : undefined;
1261
+ const assignments = initialized === undefined ? [] : [initialized];
1262
+ const others = context.bindings.named(name.text).filter((node) => node !== name);
1263
+ const symbols = await checker.symbolsAt(others);
1264
+ for (const [position, node] of others.entries()) {
1265
+ const found = symbols[position];
1266
+ const same = found !== undefined && symbol !== undefined && checker.same(found, symbol);
1267
+ const assignment = await assignmentAt(context, node, same, declaration);
1268
+ if (assignment !== undefined)
1269
+ assignments.push(assignment.value);
1270
+ }
1271
+ return assignments;
1272
+ }
1273
+ async function fieldHeld(context, access, path, depth) {
1274
+ const { ts, checker } = context;
1275
+ const [symbol] = await checker.symbolsAt([access.name]);
1276
+ const declaration = symbol === undefined ? undefined : await checker.declaration(symbol);
1277
+ const assignments = await fieldAssignments(context, access.name, declaration);
1278
+ const injected = declaration !== undefined && ts.isParameter(declaration);
1279
+ if (assignments.length === 0)
1280
+ return "unseen";
1281
+ const [only] = assignments;
1282
+ if (injected || assignments.length > 1 || only === undefined || path.length > 0 || context.bindings.fileAlone)
1283
+ return "unknown";
1284
+ return held(context, only, path, depth + 1);
1285
+ }
1286
+ async function held(context, node, path, depth) {
1287
+ const { ts } = context;
1288
+ if (depth > MAX_DEPTH)
1289
+ return "unknown";
1290
+ const value = unwrapped(ts, node);
1291
+ const [name, ...rest] = path;
1292
+ if (ts.isObjectLiteralExpression(value) && name !== undefined) {
1293
+ const property = ownProperty(ts, value, name);
1294
+ if (typeof property !== "string")
1295
+ return held(context, property.node, rest, depth + 1);
1296
+ return property === "absent" && rest.length === 0 ? "absent" : "unknown";
1297
+ }
1298
+ if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.name)) {
1299
+ if (value.expression.kind === ts.SyntaxKind.ThisKeyword)
1300
+ return fieldHeld(context, value, path, depth);
1301
+ return held(context, value.expression, [value.name.text, ...path], depth + 1);
1302
+ }
1303
+ if (ts.isIdentifier(value))
1304
+ return variableHeld(context, value, path, depth);
1305
+ if (name === undefined)
1306
+ return { node: value };
1307
+ return ts.isMetaProperty(value) ? "unseen" : "unknown";
1308
+ }
1309
+ function heldAt(context, node, ...path) {
1310
+ return held(context, node, path, 0);
1311
+ }
1312
+ async function heldParts(context, value) {
1313
+ if (value === "unseen")
1314
+ return [configuredPart];
1315
+ return typeof value === "string" ? [computedPart] : urlParts(context, value.node);
1316
+ }
1317
+ async function urlParts(context, node, depth = 0) {
1318
+ const { ts } = context;
1319
+ if (depth > MAX_DEPTH)
1320
+ return [computedPart];
1321
+ const value = unwrapped(ts, node);
1322
+ if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value))
1323
+ return [{ kind: "text", text: value.text }];
1324
+ if (ts.isTemplateExpression(value)) {
1325
+ const parts = [{ kind: "text", text: value.head.text }];
1326
+ for (const span of value.templateSpans) {
1327
+ parts.push(...await urlParts(context, span.expression, depth + 1), { kind: "text", text: span.literal.text });
1328
+ }
1329
+ return parts;
1330
+ }
1331
+ if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.PlusToken) {
1332
+ return [...await urlParts(context, value.left, depth + 1), ...await urlParts(context, value.right, depth + 1)];
1333
+ }
1334
+ if (!ts.isIdentifier(value) && !ts.isPropertyAccessExpression(value))
1335
+ return [computedPart];
1336
+ const resolved = await held(context, value, [], depth + 1);
1337
+ return typeof resolved === "string" ? heldParts(context, resolved) : urlParts(context, resolved.node, depth + 1);
1338
+ }
1339
+ async function literalText(context, node) {
1340
+ if (node === undefined)
1341
+ return;
1342
+ const [first, ...rest] = await urlParts(context, node);
1343
+ return rest.length === 0 && first?.kind === "text" ? first.text : undefined;
1344
+ }
1345
+ async function methodName(context, node) {
1346
+ return methodText(await literalText(context, node));
1347
+ }
1348
+
1349
+ // plugins/scanners/http-clients.ts
1350
+ async function declaredMethod(context, options, fallback) {
1351
+ if (options === undefined)
1352
+ return fallback;
1353
+ const declared = await heldAt(context, options, "method");
1354
+ if (declared === "absent")
1355
+ return fallback;
1356
+ return typeof declared === "string" ? undefined : methodName(context, declared.node);
1357
+ }
1358
+ async function optionsBase(context, config, fallback) {
1359
+ if (config === undefined)
1360
+ return fallback;
1361
+ const base = await heldAt(context, config, "baseURL");
1362
+ return base === "absent" ? fallback : heldParts(context, base);
1363
+ }
1364
+ async function runtimeFetch(context, callee) {
1365
+ if (!context.ts.isIdentifier(callee) || callee.text !== "fetch")
1366
+ return;
1367
+ const origin = await importOrigin(context, callee);
1368
+ const fetch = origin === undefined ? await runtimeGlobal(context, callee) : origin.module === "node-fetch" && origin.name === "default";
1369
+ return fetch ? "fetch" : undefined;
1370
+ }
1371
+ async function fetchRequest(context, call, client) {
1372
+ const [url, options] = call.arguments;
1373
+ const kind = url === undefined ? undefined : await client(call.expression);
1374
+ if (url === undefined || kind === undefined)
1375
+ return;
1376
+ const input = await heldAt(context, url);
1377
+ const method = input === "unseen" || typeof input === "object" && urlText(context.ts, input.node) ? await declaredMethod(context, options, "GET") : undefined;
1378
+ const parts = await urlParts(context, url);
1379
+ const target = kind === "ofetch" ? withBase(await optionsBase(context, options, []), parts) : parts;
1380
+ return { ...method === undefined ? {} : { method }, ...requestUrl(target) };
1381
+ }
1382
+ async function withDefaults(context, client, holders) {
1383
+ const found = await Promise.all(holders.map((holder) => context.bindings.settings(holder, ["defaults"], ["baseURL", "method"])));
1384
+ const bases = found.flatMap((settings) => settings.assigned.get("baseURL") ?? []);
1385
+ const methods = found.flatMap((settings) => settings.assigned.get("method") ?? []);
1386
+ if (found.some((settings) => settings.changed) || bases.length > 1 || methods.length > 1)
1387
+ return { base: [computedPart], method: undefined };
1388
+ return {
1389
+ base: bases[0] === undefined ? client.base : await urlParts(context, bases[0]),
1390
+ method: methods[0] === undefined ? client.method : methodText(await literalText(context, methods[0]))
1391
+ };
1392
+ }
1393
+ function defaultClient(context) {
1394
+ return withDefaults(context, { base: [], method: "GET" }, context.bindings.defaultImports("axios"));
1395
+ }
1396
+ async function sent(context, client, url, method, config) {
1397
+ const effective = method ?? await declaredMethod(context, config, client.method);
1398
+ const base = await optionsBase(context, config, client.base);
1399
+ return { ...effective === undefined ? {} : { method: effective }, ...requestUrl(joinBase(base, url)) };
1400
+ }
1401
+ async function configRequest(context, client, config) {
1402
+ const url = config === undefined ? "absent" : await heldAt(context, config, "url");
1403
+ return url === "absent" ? undefined : sent(context, client, await heldParts(context, url), undefined, config);
1404
+ }
1405
+ async function holdsObject(context, node) {
1406
+ const value = await heldAt(context, node);
1407
+ return typeof value === "object" && context.ts.isObjectLiteralExpression(value.node);
1408
+ }
1409
+ var SHORTHAND = new Map([
1410
+ ["get", "GET"],
1411
+ ["post", "POST"],
1412
+ ["put", "PUT"],
1413
+ ["patch", "PATCH"],
1414
+ ["delete", "DELETE"],
1415
+ ["head", "HEAD"],
1416
+ ["options", "OPTIONS"]
1417
+ ]);
1418
+ var WITH_BODY = new Set(["post", "put", "patch"]);
1419
+ async function isAxios(context, node) {
1420
+ const origin = await importOrigin(context, node);
1421
+ return origin?.module === "axios" && origin.name === "default";
1422
+ }
1423
+ async function createdClient(context, node) {
1424
+ const { ts } = context;
1425
+ const created = await heldAt(context, node);
1426
+ const call = typeof created === "object" && ts.isCallExpression(created.node) ? created.node : undefined;
1427
+ const callee = call?.expression;
1428
+ if (call === undefined || callee === undefined || !ts.isPropertyAccessExpression(callee) || !ts.isIdentifier(callee.name))
1429
+ return;
1430
+ if (callee.name.text !== "create" || !await isAxios(context, callee.expression))
1431
+ return;
1432
+ const parent = await defaultClient(context);
1433
+ const inherited = { base: parent.base.length === 0 ? [] : [computedPart], method: parent.method === "GET" ? "GET" : undefined };
1434
+ const [config] = call.arguments;
1435
+ const client = { base: await optionsBase(context, config, inherited.base), method: await declaredMethod(context, config, inherited.method) };
1436
+ const holders = [await declarationOf(context, node), holderOf(ts, call)].filter((holder) => holder !== undefined);
1437
+ return withDefaults(context, client, [...new Set(holders)]);
1438
+ }
1439
+ async function axiosClient(context, node) {
1440
+ if (!context.ts.isIdentifier(node))
1441
+ return;
1442
+ return await isAxios(context, node) ? defaultClient(context) : createdClient(context, node);
1443
+ }
1444
+ async function axiosRequest(context, call) {
1445
+ const { ts } = context;
1446
+ const callee = call.expression;
1447
+ const [first, second, third] = call.arguments;
1448
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
1449
+ const client = await axiosClient(context, callee.expression);
1450
+ if (client === undefined)
1451
+ return;
1452
+ const member = callee.name.text;
1453
+ const method = SHORTHAND.get(member);
1454
+ if (method !== undefined && first !== undefined) {
1455
+ return sent(context, client, await urlParts(context, first), method, WITH_BODY.has(member) ? third : second);
1456
+ }
1457
+ return member === "request" ? configRequest(context, client, first) : undefined;
1458
+ }
1459
+ const client = await axiosClient(context, callee);
1460
+ if (client === undefined || first === undefined)
1461
+ return;
1462
+ if (await holdsObject(context, first))
1463
+ return configRequest(context, client, first);
1464
+ return sent(context, client, await urlParts(context, first), undefined, second);
1465
+ }
1466
+
1467
+ // plugins/scanners/http-checker.ts
1468
+ function primitive(ts, type) {
1469
+ if (type.isUnion())
1470
+ return (type.types ?? []).every((member) => primitive(ts, member));
1471
+ const flags = ts.TypeFlags;
1472
+ const mask = flags.StringLike | flags.NumberLike | flags.BigIntLike | flags.BooleanLike | flags.EnumLike | flags.ESSymbolLike | flags.VoidLike | flags.Null;
1473
+ return (type.flags & mask) !== 0;
1474
+ }
1475
+ function classicChecker(ts, checker) {
1476
+ const symbol = (handle) => handle;
1477
+ return {
1478
+ symbolsAt: async (nodes) => nodes.map((node) => checker.getSymbolAtLocation(node)),
1479
+ shorthandValue: async (property) => checker.getShorthandAssignmentValueSymbol(property),
1480
+ aliased: async (handle) => symbol(handle).flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol(handle)) : handle,
1481
+ module: (handle) => (symbol(handle).flags & ts.SymbolFlags.ValueModule) !== 0,
1482
+ declaration: async (handle) => symbol(handle).valueDeclaration ?? symbol(handle).declarations?.[0],
1483
+ declarationCount: (handle) => symbol(handle).declarations?.length ?? 0,
1484
+ exportsOf: async (handle) => checker.getExportsOfModule(symbol(handle)),
1485
+ primitiveAt: async (node) => primitive(ts, checker.getTypeAtLocation(node)),
1486
+ same: (left, right) => left === right
1487
+ };
1488
+ }
1489
+
1490
+ // plugins/scanners/react/src/http.ts
1491
+ async function reactHttpRequests(sources, analyzed, checker, callerOperation) {
1492
+ const context = urlContext(ts3, classicChecker(ts3, checker), analyzed);
1493
+ const calls = [];
1494
+ const visit = (node) => {
1495
+ if (ts3.isCallExpression(node))
1496
+ calls.push(node);
1497
+ ts3.forEachChild(node, visit);
1498
+ };
1499
+ for (const source of sources)
1500
+ visit(source);
1501
+ const requests = [];
1502
+ for (const call of calls) {
1503
+ const request = await fetchRequest(context, call, (callee) => runtimeFetch(context, callee)) ?? await axiosRequest(context, call);
1504
+ const operation = request === undefined ? undefined : callerOperation(call);
1505
+ if (request !== undefined && operation !== undefined)
1506
+ requests.push({ operation, ...request });
1507
+ }
1508
+ return requests;
1509
+ }
1510
+
244
1511
  // plugins/scanners/react/src/scan.ts
245
1512
  function relative(root, file) {
246
- return path2.relative(root, file).split(path2.sep).join("/");
1513
+ return path5.relative(root, file).split(path5.sep).join("/");
247
1514
  }
248
1515
  function failDiagnostics(diagnostics) {
249
- const errors = diagnostics.filter((item) => item.category === ts.DiagnosticCategory.Error);
1516
+ const errors = diagnostics.filter((item) => item.category === ts4.DiagnosticCategory.Error);
250
1517
  if (errors.length)
251
- throw new Error(errors.map((item) => `${item.file?.fileName ?? "React"}: ${ts.flattenDiagnosticMessageText(item.messageText, `
1518
+ throw new Error(errors.map((item) => `${item.file?.fileName ?? "React"}: ${ts4.flattenDiagnosticMessageText(item.messageText, `
252
1519
  `)}`).join(`
253
1520
  `));
254
1521
  }
255
1522
  function reactProject(root, repositoryRoot = root) {
256
- const manifestFile = path2.join(root, "package.json");
257
- if (!existsSync2(manifestFile))
1523
+ const manifestFile = path5.join(root, "package.json");
1524
+ if (!existsSync3(manifestFile))
258
1525
  return;
259
1526
  const manifest = JSON.parse(readFileSync(manifestFile, "utf8"));
260
1527
  if (!hasDependency(manifest, "react"))
261
1528
  return;
262
1529
  try {
263
- createRequire(manifestFile).resolve("react");
264
- const configFile = path2.join(root, "tsconfig.json");
265
- const config = ts.readConfigFile(configFile, ts.sys.readFile);
1530
+ const configFile = path5.join(root, "tsconfig.json");
1531
+ const config = ts4.readConfigFile(configFile, ts4.sys.readFile);
266
1532
  if (config.error)
267
1533
  failDiagnostics([config.error]);
268
- const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, root, undefined, configFile);
1534
+ const parsed = ts4.parseJsonConfigFileContent(config.config, ts4.sys, root, undefined, configFile);
269
1535
  failDiagnostics(parsed.errors);
270
- const program = ts.createProgram(parsed.fileNames, { ...parsed.options, noEmit: true });
271
- const sources = program.getSourceFiles().filter((source) => {
1536
+ const program = ts4.createProgram(parsed.fileNames, { ...parsed.options, noEmit: true });
1537
+ const owned = program.getSourceFiles().filter((source) => {
272
1538
  const file = relative(repositoryRoot, source.fileName);
273
- return file.endsWith(".tsx") && !source.isDeclarationFile && !file.startsWith("../") && !file.includes("node_modules/");
1539
+ return !source.isDeclarationFile && !file.startsWith("../") && !file.includes("node_modules/");
274
1540
  });
1541
+ const sources = owned.filter((source) => source.fileName.endsWith(".tsx"));
275
1542
  if (!sources.length)
276
1543
  throw new Error("The project tsconfig.json must include React TSX source files.");
277
- const react = ts.resolveModuleName("react", sources[0].fileName, parsed.options, ts.sys).resolvedModule;
278
- if (!react?.resolvedFileName.endsWith(".d.ts"))
279
- throw new Error("Install @types/react in the project.");
280
- failDiagnostics(program.getOptionsDiagnostics());
1544
+ const routers = nextRouters(root);
1545
+ const routes = hasDependency(manifest, "next") ? owned.filter((source) => routeLocation(relative(root, source.fileName), routers) !== undefined) : [];
281
1546
  failDiagnostics(program.getSyntacticDiagnostics());
282
- for (const source of sources)
283
- failDiagnostics(program.getSemanticDiagnostics(source));
284
- return { manifest, program, sources };
1547
+ return { manifest, program, owned, sources, routes, routers };
285
1548
  } catch (error) {
286
- throw new Error(`REACT_PROJECT_PREPARATION: Install the project dependencies with its declared package manager and lockfile; provide a valid project tsconfig.json and type-correct TSX files. ${error}`);
1549
+ throw new Error(`REACT_SOURCE_INVALID: Check the project tsconfig.json and TSX syntax. ${error}`);
287
1550
  }
288
1551
  }
289
- function executable(node) {
290
- return ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) && !!node.body;
291
- }
292
1552
  function caller(node) {
293
1553
  for (let parent = node.parent;parent; parent = parent.parent)
294
1554
  if (executable(parent))
@@ -296,20 +1556,20 @@ function caller(node) {
296
1556
  return;
297
1557
  }
298
1558
  function writesSymbol(target, symbol, checker) {
299
- if (ts.isIdentifier(target))
1559
+ if (ts4.isIdentifier(target))
300
1560
  return checker.getSymbolAtLocation(target) === symbol;
301
- if (ts.isShorthandPropertyAssignment(target))
1561
+ if (ts4.isShorthandPropertyAssignment(target))
302
1562
  return checker.getShorthandAssignmentValueSymbol(target) === symbol;
303
- if (ts.isArrayLiteralExpression(target))
1563
+ if (ts4.isArrayLiteralExpression(target))
304
1564
  return target.elements.some((item) => writesSymbol(item, symbol, checker));
305
- if (ts.isObjectLiteralExpression(target))
1565
+ if (ts4.isObjectLiteralExpression(target))
306
1566
  return target.properties.some((item) => writesSymbol(item, symbol, checker));
307
- if (ts.isPropertyAssignment(target))
1567
+ if (ts4.isPropertyAssignment(target))
308
1568
  return writesSymbol(target.initializer, symbol, checker);
309
- if (ts.isSpreadElement(target) || ts.isSpreadAssignment(target) || ts.isParenthesizedExpression(target)) {
1569
+ if (ts4.isSpreadElement(target) || ts4.isSpreadAssignment(target) || ts4.isParenthesizedExpression(target)) {
310
1570
  return writesSymbol(target.expression, symbol, checker);
311
1571
  }
312
- if (ts.isBinaryExpression(target) && target.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
1572
+ if (ts4.isBinaryExpression(target) && target.operatorToken.kind === ts4.SyntaxKind.EqualsToken) {
313
1573
  return writesSymbol(target.left, symbol, checker);
314
1574
  }
315
1575
  return false;
@@ -327,24 +1587,24 @@ class Evidence {
327
1587
  this.checker = checker;
328
1588
  this.files = new Set(sources);
329
1589
  }
330
- operation(node) {
1590
+ operationId(node) {
331
1591
  const file = relative(this.root, node.getSourceFile().fileName);
332
1592
  const position = node.getStart();
333
1593
  const id = `${file}#${position}`;
334
- const name = node.name?.text ?? (ts.isVariableDeclaration(node.parent) ? node.parent.name.getText() : "callback");
1594
+ const name = node.name?.text ?? (ts4.isVariableDeclaration(node.parent) ? node.parent.name.getText() : "callback");
335
1595
  this.operations.set(id, { id, file, name, position });
336
1596
  return id;
337
1597
  }
338
1598
  functionValue(name) {
339
1599
  let symbol = this.checker.getSymbolAtLocation(name);
340
- if (symbol && symbol.flags & ts.SymbolFlags.Alias)
1600
+ if (symbol && symbol.flags & ts4.SymbolFlags.Alias)
341
1601
  symbol = this.checker.getAliasedSymbol(symbol);
342
1602
  const declaration = symbol?.valueDeclaration;
343
1603
  if (!declaration || !this.files.has(declaration.getSourceFile()))
344
1604
  return;
345
1605
  if (executable(declaration))
346
1606
  return declaration;
347
- if (ts.isVariableDeclaration(declaration) && declaration.parent.flags & ts.NodeFlags.Const && declaration.initializer && executable(declaration.initializer))
1607
+ if (ts4.isVariableDeclaration(declaration) && declaration.parent.flags & ts4.NodeFlags.Const && declaration.initializer && executable(declaration.initializer))
348
1608
  return declaration.initializer;
349
1609
  return;
350
1610
  }
@@ -360,10 +1620,10 @@ class Evidence {
360
1620
  }
361
1621
  propCalls(component, attribute) {
362
1622
  const parameter = component.parameters[0];
363
- if (!parameter || !ts.isObjectBindingPattern(parameter.name))
1623
+ if (!parameter || !ts4.isObjectBindingPattern(parameter.name))
364
1624
  return [];
365
1625
  const binding = parameter.name.elements.find((element) => !element.dotDotDotToken && !element.initializer && (element.propertyName ?? element.name).getText() === attribute.name.getText());
366
- if (!binding || !ts.isIdentifier(binding.name))
1626
+ if (!binding || !ts4.isIdentifier(binding.name))
367
1627
  return [];
368
1628
  const symbol = this.checker.getSymbolAtLocation(binding.name);
369
1629
  if (!symbol)
@@ -372,25 +1632,25 @@ class Evidence {
372
1632
  let reassigned = false;
373
1633
  const checker = this.checker;
374
1634
  const visit = (node) => {
375
- if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && writesSymbol(node.left, symbol, checker))
1635
+ if (ts4.isBinaryExpression(node) && node.operatorToken.kind >= ts4.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts4.SyntaxKind.LastAssignment && writesSymbol(node.left, symbol, checker))
376
1636
  reassigned = true;
377
- if (ts.isForOfStatement(node) && writesSymbol(node.initializer, symbol, checker))
1637
+ if (ts4.isForOfStatement(node) && writesSymbol(node.initializer, symbol, checker))
378
1638
  reassigned = true;
379
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && checker.getSymbolAtLocation(node.expression) === symbol)
1639
+ if (ts4.isCallExpression(node) && ts4.isIdentifier(node.expression) && checker.getSymbolAtLocation(node.expression) === symbol)
380
1640
  calls.push(node);
381
- ts.forEachChild(node, visit);
1641
+ ts4.forEachChild(node, visit);
382
1642
  };
383
1643
  visit(component);
384
1644
  return reassigned ? [] : calls;
385
1645
  }
386
1646
  binding(component, attribute) {
387
1647
  const initializer = attribute.initializer;
388
- if (!initializer || !ts.isJsxExpression(initializer) || !initializer.expression)
1648
+ if (!initializer || !ts4.isJsxExpression(initializer) || !initializer.expression)
389
1649
  return;
390
1650
  const expression = initializer.expression;
391
1651
  if (!this.checker.getTypeAtLocation(expression).getCallSignatures().length)
392
1652
  return;
393
- const target = ts.isIdentifier(expression) ? this.functionValue(expression) : undefined;
1653
+ const target = ts4.isIdentifier(expression) ? this.functionValue(expression) : undefined;
394
1654
  const calls = this.propCalls(component, attribute);
395
1655
  if (!target || !calls.length) {
396
1656
  this.unsupported(attribute);
@@ -402,8 +1662,8 @@ class Evidence {
402
1662
  if (!operation)
403
1663
  continue;
404
1664
  this.invocations.push({
405
- source: this.operation(operation),
406
- targets: [this.operation(target)],
1665
+ source: this.operationId(operation),
1666
+ targets: [this.operationId(target)],
407
1667
  unresolved: false,
408
1668
  member: attribute.name.getText(),
409
1669
  position: call.getStart(),
@@ -420,30 +1680,28 @@ class Evidence {
420
1680
  const component = this.functionValue(node.tagName);
421
1681
  if (!component)
422
1682
  return;
423
- if (node.attributes.properties.some(ts.isJsxSpreadAttribute)) {
1683
+ if (node.attributes.properties.some(ts4.isJsxSpreadAttribute)) {
424
1684
  this.unsupported(node);
425
1685
  return;
426
1686
  }
427
1687
  for (const attribute of node.attributes.properties)
428
- if (ts.isJsxAttribute(attribute))
1688
+ if (ts4.isJsxAttribute(attribute))
429
1689
  this.binding(component, attribute);
430
1690
  }
431
1691
  inspect(node) {
432
- if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node))
1692
+ if (ts4.isJsxSelfClosingElement(node) || ts4.isJsxOpeningElement(node))
433
1693
  this.element(node);
434
- ts.forEachChild(node, (child) => this.inspect(child));
1694
+ ts4.forEachChild(node, (child) => this.inspect(child));
435
1695
  }
436
1696
  }
437
1697
  async function checkReactReadiness(root) {
438
- const projects = await frameworkProjects(root, "react");
439
- if (!projects.length)
440
- throw new Error("REACT_PROJECT_REQUIRED: No React project declaration was found.");
1698
+ const projects = await frameworkProjects(root, "react", [".tsx"]);
441
1699
  for (const project of projects)
442
1700
  reactProject(project, root);
443
1701
  }
444
1702
  async function scanReact(root) {
445
1703
  const parts = [];
446
- for (const project of await frameworkProjects(root, "react")) {
1704
+ for (const project of await frameworkProjects(root, "react", [".tsx"])) {
447
1705
  const observation = await scanReactProject(project, root);
448
1706
  if (observation)
449
1707
  parts.push({ key: relative(root, project), observation });
@@ -454,18 +1712,25 @@ async function scanReactProject(projectRoot, root) {
454
1712
  const project = reactProject(projectRoot, root);
455
1713
  if (!project)
456
1714
  return;
457
- const { manifest, program, sources } = project;
1715
+ const { manifest, program, owned, sources, routes, routers } = project;
458
1716
  const evidence = new Evidence(root, program.getTypeChecker(), sources);
459
1717
  for (const source of sources)
460
1718
  evidence.inspect(source);
461
- const files = sources.map((source) => ({ file: relative(root, source.fileName), symbols: [] }));
1719
+ const httpRequests = await reactHttpRequests(sources, owned, program.getTypeChecker(), (call) => {
1720
+ const operation = caller(call);
1721
+ return operation === undefined ? undefined : evidence.operationId(operation);
1722
+ });
1723
+ const httpEndpoints = nextRouteEndpoints(routes, projectRoot, routers, (handler) => evidence.operationId(handler));
1724
+ const files = [...new Set([...sources, ...routes].map((source) => relative(root, source.fileName)))].map((file) => ({ file, symbols: [] }));
462
1725
  return createScanObservation({
463
- scanner: { id: "react", technology: "typescript/react", engine: "typescript-sdk", engineVersion: ts.version },
464
- roots: [{ id: "react-project", kind: "package", name: manifest.name, file: relative(root, path2.join(projectRoot, "package.json")) }],
1726
+ scanner: { id: "react", technology: "typescript/react", engine: "typescript-sdk", engineVersion: ts4.version },
1727
+ roots: [{ id: "react-project", kind: "package", name: manifest.name, file: relative(root, path5.join(projectRoot, "package.json")) }],
465
1728
  files: files.map((file) => ({ ...file, roots: ["react-project"] })),
466
1729
  operations: [...evidence.operations.values()],
467
1730
  invocations: evidence.invocations,
468
- diagnostics: evidence.diagnostics
1731
+ diagnostics: evidence.diagnostics,
1732
+ ...httpEndpoints.length ? { httpEndpoints } : {},
1733
+ ...httpRequests.length ? { httpRequests } : {}
469
1734
  });
470
1735
  }
471
1736
 
@@ -474,6 +1739,14 @@ var src_default = {
474
1739
  id: "react",
475
1740
  watch: { include: ["**/*.tsx", "**/*.ts", "**/tsconfig*.json", "**/package.json"], exclude: [] },
476
1741
  checkReadiness: checkReactReadiness,
1742
+ listSourceFiles: (root) => frameworkSourceFiles({
1743
+ root,
1744
+ dependency: "react",
1745
+ projects: [".tsx"],
1746
+ sources: [".tsx"],
1747
+ also: routeCandidate
1748
+ }),
1749
+ readCodeStructure: (root, references) => readTypeScriptOutline(ts5, root, references),
477
1750
  scan: scanReact
478
1751
  };
479
1752
  export {