@groma/scanner-vue 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 +155 -8
  2. package/dist/index.js +1675 -39
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -39605,6 +39605,116 @@ var require_typescript = __commonJS(function(exports) {
39605
39605
  __exportStar(require_createSys(), exports);
39606
39606
  });
39607
39607
 
39608
+ // packages/scanner/src/values.ts
39609
+ function object(value, label) {
39610
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
39611
+ throw new Error(`${label} must be an object`);
39612
+ }
39613
+ return value;
39614
+ }
39615
+ function string(value, label) {
39616
+ if (typeof value !== "string")
39617
+ throw new Error(`${label} must be a string`);
39618
+ return value;
39619
+ }
39620
+ function array(value, label) {
39621
+ if (!Array.isArray(value))
39622
+ throw new Error(`${label} must be an array`);
39623
+ return value;
39624
+ }
39625
+
39626
+ // packages/scanner/src/http.ts
39627
+ var pathText = /^[A-Za-z0-9\-._~!$&'()*+,;=:@%]+$/;
39628
+ var methodToken = /^[A-Z][A-Z-]*$/;
39629
+ function fail(message) {
39630
+ throw new Error(message);
39631
+ }
39632
+ function text(value, label) {
39633
+ if (typeof value !== "string" || !pathText.test(value))
39634
+ fail(`${label} must be nonempty URL path characters`);
39635
+ return value;
39636
+ }
39637
+ function method(value, label, anyMethod) {
39638
+ if (typeof value !== "string" || !(methodToken.test(value) || anyMethod && value === "*")) {
39639
+ fail(`${label} must be an uppercase HTTP method${anyMethod ? " or *" : ""}`);
39640
+ }
39641
+ return value;
39642
+ }
39643
+ function operation(value, operations) {
39644
+ if (typeof value !== "string" || !operations.has(value))
39645
+ fail("HTTP fact references unknown operation");
39646
+ return value;
39647
+ }
39648
+ function endpointSegment(value, last) {
39649
+ const segment = object(value, "HTTP endpoint segment");
39650
+ if (segment.kind === "literal")
39651
+ return { kind: "literal", value: text(segment.value, "HTTP literal segment") };
39652
+ if (segment.kind !== "parameter" && segment.kind !== "catch-all")
39653
+ fail(`unknown HTTP endpoint segment kind: ${String(segment.kind)}`);
39654
+ if (segment.kind === "catch-all" && !last)
39655
+ fail("an HTTP catch-all segment must be last");
39656
+ for (const flag of ["optional", "constrained"]) {
39657
+ if (segment[flag] !== undefined && typeof segment[flag] !== "boolean")
39658
+ fail(`HTTP segment ${flag} must be a boolean`);
39659
+ }
39660
+ return {
39661
+ kind: segment.kind,
39662
+ name: text(segment.name, "HTTP segment name"),
39663
+ ...segment.optional ? { optional: true } : {},
39664
+ ...segment.constrained ? { constrained: true } : {}
39665
+ };
39666
+ }
39667
+ function requestSegment(value) {
39668
+ const segment = object(value, "HTTP request segment");
39669
+ if (segment.kind === "literal")
39670
+ return { kind: "literal", value: text(segment.value, "HTTP literal segment") };
39671
+ if (segment.kind === "dynamic" || segment.kind === "unknown")
39672
+ return { kind: segment.kind };
39673
+ return fail(`unknown HTTP request segment kind: ${String(segment.kind)}`);
39674
+ }
39675
+ function order(value) {
39676
+ const fact = object(value, "HTTP endpoint order");
39677
+ const application = string(fact.application, "HTTP endpoint order application");
39678
+ if (application === "")
39679
+ fail("HTTP endpoint order application must be nonempty");
39680
+ const position = fact.position;
39681
+ if (typeof position !== "number" || !Number.isInteger(position) || position < 0) {
39682
+ fail("HTTP endpoint order position must be a nonnegative integer");
39683
+ }
39684
+ return { application, position };
39685
+ }
39686
+ function endpoint(value, operations) {
39687
+ const fact = object(value, "HTTP endpoint");
39688
+ const path = array(fact.path, "HTTP endpoint path");
39689
+ return {
39690
+ operation: operation(fact.operation, operations),
39691
+ method: method(fact.method, "HTTP endpoint method", true),
39692
+ path: path.map((segment, index) => endpointSegment(segment, index === path.length - 1)),
39693
+ ...fact.order === undefined ? {} : { order: order(fact.order) }
39694
+ };
39695
+ }
39696
+ function request(value, operations) {
39697
+ const fact = object(value, "HTTP request");
39698
+ if (fact.configured !== undefined && typeof fact.configured !== "boolean")
39699
+ fail("HTTP request configured must be a boolean");
39700
+ return {
39701
+ operation: operation(fact.operation, operations),
39702
+ ...fact.method === undefined ? {} : { method: method(fact.method, "HTTP request method", false) },
39703
+ ...fact.configured ? { configured: true } : {},
39704
+ path: array(fact.path, "HTTP request path").map(requestSegment)
39705
+ };
39706
+ }
39707
+ function httpEvidence(endpoints, requests, operations) {
39708
+ if (endpoints === undefined && requests === undefined)
39709
+ return {};
39710
+ if (operations === undefined)
39711
+ fail("HTTP facts require operation declarations");
39712
+ return {
39713
+ ...endpoints === undefined ? {} : { httpEndpoints: array(endpoints, "httpEndpoints").map((fact) => endpoint(fact, operations)) },
39714
+ ...requests === undefined ? {} : { httpRequests: array(requests, "httpRequests").map((fact) => request(fact, operations)) }
39715
+ };
39716
+ }
39717
+
39608
39718
  // packages/scanner/src/index.ts
39609
39719
  function compare(...values) {
39610
39720
  return values.join("\x00");
@@ -39648,7 +39758,9 @@ function createScanObservation(input) {
39648
39758
  scanner: input.scanner,
39649
39759
  roots,
39650
39760
  files,
39761
+ ...sourceUnits(input.sourceUnits, filePaths),
39651
39762
  ...operationEvidence(input, filePaths),
39763
+ ...httpEvidence(input.httpEndpoints, input.httpRequests, input.operations && new Set(input.operations.map((operation) => operation.id))),
39652
39764
  diagnostics: [...new Map(input.diagnostics.map((diagnostic) => [
39653
39765
  diagnosticKey(diagnostic),
39654
39766
  diagnostic
@@ -39657,6 +39769,23 @@ function createScanObservation(input) {
39657
39769
  })
39658
39770
  };
39659
39771
  }
39772
+ function sourceUnits(input, paths) {
39773
+ if (input === undefined)
39774
+ return {};
39775
+ const units = array(input, "sourceUnits").map((entry) => {
39776
+ const unit = object(entry, "source unit");
39777
+ const primary = string(unit.primary, "source unit.primary");
39778
+ const files = [...new Set(array(unit.files, "source unit.files").map((file) => string(file, "source unit.file")))].sort();
39779
+ if (!files.includes(primary))
39780
+ throw new Error(`source unit omits primary file: ${primary}`);
39781
+ for (const file of files) {
39782
+ if (!paths.has(file))
39783
+ throw new Error(`source unit references unknown file: ${file}`);
39784
+ }
39785
+ return { primary, files };
39786
+ });
39787
+ 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))) };
39788
+ }
39660
39789
  function validateRoots(input) {
39661
39790
  const roots = uniqueBy(input, (root) => root.id, "root id");
39662
39791
  const byId = new Map(roots.map((root) => [root.id, root]));
@@ -39687,16 +39816,6 @@ function diagnosticLocation(value) {
39687
39816
  ...value.line === undefined ? {} : { line: Number(value.line) }
39688
39817
  };
39689
39818
  }
39690
- function string(value, label) {
39691
- if (typeof value !== "string")
39692
- throw new Error(`${label} must be a string`);
39693
- return value;
39694
- }
39695
- function array(value, label) {
39696
- if (!Array.isArray(value))
39697
- throw new Error(`${label} must be an array`);
39698
- return value;
39699
- }
39700
39819
  function operationEvidence(input, files) {
39701
39820
  if (input.operations === undefined && input.invocations !== undefined)
39702
39821
  throw new Error("invocations require operation declarations");
@@ -39776,7 +39895,6 @@ import ts2 from "./typescript.cjs";
39776
39895
  var import_typescript = __toESM(require_typescript(), 1);
39777
39896
  var import_language_core = __toESM(require_language_core2(), 1);
39778
39897
  import { existsSync as existsSync2, readFileSync } from "fs";
39779
- import { createRequire } from "module";
39780
39898
  import path2 from "path";
39781
39899
  import ts from "./typescript.cjs";
39782
39900
 
@@ -39803,16 +39921,32 @@ var excluded = new Set([
39803
39921
  "groma",
39804
39922
  ".groma"
39805
39923
  ]);
39806
- async function projectFiles(root, matches) {
39924
+ async function repositoryFiles(root, matches) {
39807
39925
  const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
39808
- return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && !file.split("/").some((part) => excluded.has(part)) && existsSync(path.join(root, file))))].sort();
39926
+ return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && existsSync(path.join(root, file))))].sort();
39927
+ }
39928
+ function projectFiles(root, matches) {
39929
+ return repositoryFiles(root, (file) => matches(file) && !file.split("/").some((part) => excluded.has(part)));
39809
39930
  }
39810
39931
  function hasDependency(manifest, dependency) {
39811
39932
  return ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"].some((section) => typeof manifest[section]?.[dependency] === "string");
39812
39933
  }
39813
- async function frameworkProjects(root, dependency) {
39934
+ async function frameworkProjects(root, dependency, extensions) {
39935
+ const files = await projectFiles(root, () => true);
39936
+ const manifests = files.filter((file) => path.posix.basename(file) === "package.json");
39937
+ const directories = new Set(manifests.map((file) => path.posix.dirname(file)));
39938
+ const sources = new Set;
39939
+ for (const file of files.filter((file) => extensions.some((extension) => file.endsWith(extension)) && !file.endsWith(".d.ts"))) {
39940
+ let directory = path.posix.dirname(file);
39941
+ while (directory !== "." && !directories.has(directory))
39942
+ directory = path.posix.dirname(directory);
39943
+ sources.add(directory);
39944
+ }
39814
39945
  const projects = [];
39815
- for (const file of await projectFiles(root, (file) => path.posix.basename(file) === "package.json")) {
39946
+ for (const file of manifests) {
39947
+ const directory = path.posix.dirname(file);
39948
+ if (!sources.has(directory) || !files.includes(path.posix.join(directory, "tsconfig.json")))
39949
+ continue;
39816
39950
  const manifest = JSON.parse(await readFile(path.join(root, file), "utf8"));
39817
39951
  if (hasDependency(manifest, dependency))
39818
39952
  projects.push(path.dirname(path.join(root, file)));
@@ -39880,10 +40014,32 @@ class VueProject {
39880
40014
  const code = this.language.scripts.get(file)?.generated?.root;
39881
40015
  return code instanceof import_language_core.VueVirtualCode ? code : undefined;
39882
40016
  }
40017
+ sourceUnit(file) {
40018
+ const sfc = this.sfc(file);
40019
+ if (!sfc)
40020
+ return;
40021
+ const primary = relative(this.root, file);
40022
+ const blocks = [sfc.ir.script, sfc.ir.template, ...sfc.ir.styles];
40023
+ const companions = blocks.flatMap((block) => {
40024
+ const src = block?.attrs.src;
40025
+ if (typeof src !== "string" || !src.startsWith("."))
40026
+ return [];
40027
+ const filename = path2.resolve(path2.dirname(file), src);
40028
+ const member = relative(this.root, filename);
40029
+ if (member.startsWith("../"))
40030
+ return [];
40031
+ readFileSync(filename, "utf8");
40032
+ return [member];
40033
+ });
40034
+ return { primary, files: [primary, ...companions] };
40035
+ }
39883
40036
  validateSfc(file) {
39884
40037
  const sfc = this.sfc(file);
39885
40038
  if (!sfc)
39886
40039
  return;
40040
+ if (sfc.ir.scriptSetup && (sfc.ir.scriptSetup.attrs.src !== undefined || sfc.ir.script?.attrs.src !== undefined)) {
40041
+ throw new Error(`${file}: script setup cannot use src or be combined with an external script block`);
40042
+ }
39887
40043
  const errors = [...sfc.vueSfc?.errors ?? [], ...sfc.ir.template?.errors ?? []];
39888
40044
  if (errors.length)
39889
40045
  throw new Error(`${file}: ${errors.map((error) => typeof error === "string" ? error : error.message).join(`
@@ -39926,20 +40082,19 @@ function vueProject(root, repositoryRoot = root) {
39926
40082
  if (!hasDependency(manifest, "vue"))
39927
40083
  return;
39928
40084
  try {
39929
- createRequire(manifestFile).resolve("vue/package.json");
39930
40085
  return { manifest, project: new VueProject(root, repositoryRoot) };
39931
40086
  } catch (error) {
39932
- throw new Error(`VUE_PROJECT_PREPARATION: Install project dependencies with its declared package manager and lockfile; ensure project tsconfig.json and Vue sources are valid. ${error}`);
40087
+ throw new Error(`VUE_SOURCE_INVALID: Check the project tsconfig.json and Vue syntax. ${error}`);
39933
40088
  }
39934
40089
  }
39935
40090
 
39936
40091
  // plugins/scanners/vue/src/evidence.ts
39937
- function operation(node) {
39938
- return ts2.isFunctionDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node);
40092
+ function operation2(node) {
40093
+ return ts2.isFunctionDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node) || ts2.isMethodDeclaration(node);
39939
40094
  }
39940
40095
  function enclosingOperation(node) {
39941
40096
  for (let current = node.parent;current; current = current.parent) {
39942
- if (operation(current))
40097
+ if (operation2(current))
39943
40098
  return current;
39944
40099
  }
39945
40100
  return;
@@ -39951,9 +40106,11 @@ function immutable(node) {
39951
40106
  return ts2.isVariableDeclaration(parent) && ts2.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts2.NodeFlags.Const) !== 0;
39952
40107
  }
39953
40108
  function includesEvent(type, event) {
39954
- if (type.isStringLiteral())
39955
- return type.value === event;
39956
- return type.isUnion() && type.types.some((item) => item.isStringLiteral() && item.value === event);
40109
+ if (!type)
40110
+ return false;
40111
+ if (ts2.isLiteralTypeNode(type) && ts2.isStringLiteral(type.literal))
40112
+ return type.literal.text === event;
40113
+ return ts2.isUnionTypeNode(type) && type.types.some((item) => includesEvent(item, event));
39957
40114
  }
39958
40115
 
39959
40116
  class VueEvidence {
@@ -40010,7 +40167,7 @@ class VueEvidence {
40010
40167
  continue;
40011
40168
  for (const signature of this.project.checker.getTypeAtLocation(node).getCallSignatures()) {
40012
40169
  const declaration = signature.declaration;
40013
- if (declaration && operation(declaration) && immutable(declaration) && this.project.owned(declaration.getSourceFile()))
40170
+ if (declaration && operation2(declaration) && immutable(declaration) && this.project.owned(declaration.getSourceFile()))
40014
40171
  candidates.add(declaration);
40015
40172
  }
40016
40173
  }
@@ -40040,15 +40197,14 @@ class VueEvidence {
40040
40197
  emissions(sfc, event) {
40041
40198
  const symbol = this.emitSymbol(sfc);
40042
40199
  const source = this.project.program.getSourceFile(sfc.fileName);
40043
- if (!symbol || !source)
40200
+ if (!symbol || !source || !this.declaresEvent(sfc, event))
40044
40201
  return [];
40045
40202
  const calls = [];
40046
40203
  const checker = this.project.checker;
40047
40204
  function visit(node) {
40048
40205
  if (ts2.isCallExpression(node) && checker.getSymbolAtLocation(node.expression) === symbol) {
40049
40206
  const argument = node.arguments[0];
40050
- const parameter = checker.getResolvedSignature(node)?.parameters[0];
40051
- if (argument && ts2.isStringLiteral(argument) && argument.text === event && parameter && includesEvent(checker.getTypeOfSymbolAtLocation(parameter, node), event))
40207
+ if (argument && ts2.isStringLiteral(argument) && argument.text === event)
40052
40208
  calls.push(node);
40053
40209
  }
40054
40210
  ts2.forEachChild(node, visit);
@@ -40056,13 +40212,33 @@ class VueEvidence {
40056
40212
  visit(source);
40057
40213
  return calls;
40058
40214
  }
40059
- addOperation(node) {
40215
+ declaresEvent(sfc, event) {
40216
+ const setup = sfc.ir.scriptSetup;
40217
+ if (!setup)
40218
+ return false;
40219
+ const range = import_language_core2.parseScriptSetupRanges(vueTypeScript, setup.ast, this.project.options).defineEmits?.typeArg;
40220
+ if (!range)
40221
+ return false;
40222
+ let declared = false;
40223
+ function visit(node) {
40224
+ if (node.getStart(setup.ast) === range.start && ts2.isTypeLiteralNode(node)) {
40225
+ declared = node.members.some((member) => ts2.isPropertySignature(member) && (ts2.isIdentifier(member.name) || ts2.isStringLiteral(member.name)) ? member.name.text === event : ts2.isCallSignatureDeclaration(member) && includesEvent(member.parameters[0]?.type, event));
40226
+ }
40227
+ ts2.forEachChild(node, visit);
40228
+ }
40229
+ visit(setup.ast);
40230
+ return declared;
40231
+ }
40232
+ operationId(node) {
40060
40233
  const position = this.project.position(node);
40061
40234
  if (position === undefined)
40062
40235
  return;
40063
40236
  const file = relative(this.project.root, node.getSourceFile().fileName);
40064
40237
  const id = `${file}#${position}`;
40065
- const name = ts2.isFunctionDeclaration(node) ? node.name?.text : ts2.isVariableDeclaration(node.parent) ? node.parent.name.getText() : undefined;
40238
+ if (this.operations.has(id))
40239
+ return id;
40240
+ const declared = ts2.isFunctionDeclaration(node) || ts2.isMethodDeclaration(node) ? node.name : undefined;
40241
+ const name = declared !== undefined && ts2.isIdentifier(declared) ? declared.text : ts2.isVariableDeclaration(node.parent) ? node.parent.name.getText() : undefined;
40066
40242
  this.operations.set(id, { id, file, position, name: name ?? `callback at ${this.project.line(node.getSourceFile().fileName, position)}` });
40067
40243
  return id;
40068
40244
  }
@@ -40084,8 +40260,8 @@ class VueEvidence {
40084
40260
  const position = this.project.position(call);
40085
40261
  if (!caller || position === undefined)
40086
40262
  continue;
40087
- const source = this.addOperation(caller);
40088
- const targetId = this.addOperation(target);
40263
+ const source = this.operationId(caller);
40264
+ const targetId = this.operationId(target);
40089
40265
  if (!source || !targetId)
40090
40266
  continue;
40091
40267
  this.invocations.push({
@@ -40104,7 +40280,26 @@ class VueEvidence {
40104
40280
  }
40105
40281
 
40106
40282
  // plugins/scanners/vue/src/index.ts
40283
+ import path5 from "path";
40284
+
40285
+ // plugins/scanners/typescript-project.ts
40107
40286
  import path3 from "path";
40287
+ async function frameworkSourceFiles(options) {
40288
+ const { root, dependency, projects, sources, also } = options;
40289
+ const directories = (await frameworkProjects(root, dependency, projects)).map((directory) => path3.relative(root, directory).split(path3.sep).join("/") || ".");
40290
+ if (directories.length === 0)
40291
+ return [];
40292
+ const listed = [];
40293
+ for (const file of await projectFiles(root, (candidate) => !candidate.endsWith(".d.ts"))) {
40294
+ const project = directories.find((directory) => directory === "." || file.startsWith(`${directory}/`));
40295
+ if (project === undefined)
40296
+ continue;
40297
+ const local = project === "." ? file : file.slice(project.length + 1);
40298
+ if (sources.some((extension) => file.endsWith(extension)) || (also?.(local) ?? false))
40299
+ listed.push(file);
40300
+ }
40301
+ return listed.sort();
40302
+ }
40108
40303
 
40109
40304
  // plugins/scanners/observations.ts
40110
40305
  function combineObservations(parts) {
@@ -40115,6 +40310,9 @@ function combineObservations(parts) {
40115
40310
  const operations = [];
40116
40311
  const invocations = [];
40117
40312
  const diagnostics = [];
40313
+ const sourceUnits = [];
40314
+ const httpEndpoints = [];
40315
+ const httpRequests = [];
40118
40316
  for (const { key, observation } of parts) {
40119
40317
  const id = (value) => JSON.stringify([key, value]);
40120
40318
  roots.push(...observation.roots.map((root) => ({ ...root, id: id(root.id), ...root.parent ? { parent: id(root.parent) } : {} })));
@@ -40126,7 +40324,10 @@ function combineObservations(parts) {
40126
40324
  }
40127
40325
  operations.push(...(observation.operations ?? []).map((operation) => ({ ...operation, id: id(operation.id) })));
40128
40326
  invocations.push(...(observation.invocations ?? []).map((call) => ({ ...call, source: id(call.source), targets: call.targets.map(id) })));
40327
+ httpEndpoints.push(...(observation.httpEndpoints ?? []).map((endpoint) => ({ ...endpoint, operation: id(endpoint.operation) })));
40328
+ httpRequests.push(...(observation.httpRequests ?? []).map((request) => ({ ...request, operation: id(request.operation) })));
40129
40329
  diagnostics.push(...observation.diagnostics);
40330
+ sourceUnits.push(...observation.sourceUnits ?? []);
40130
40331
  }
40131
40332
  return createScanObservation({
40132
40333
  scanner: parts[0].observation.scanner,
@@ -40134,14 +40335,1424 @@ function combineObservations(parts) {
40134
40335
  files: [...files.values()],
40135
40336
  operations,
40136
40337
  invocations,
40137
- diagnostics
40338
+ diagnostics,
40339
+ ...parts.some((part) => part.observation.sourceUnits !== undefined) ? { sourceUnits } : {},
40340
+ ...parts.some((part) => part.observation.httpEndpoints !== undefined) ? { httpEndpoints } : {},
40341
+ ...parts.some((part) => part.observation.httpRequests !== undefined) ? { httpRequests } : {}
40342
+ });
40343
+ }
40344
+
40345
+ // plugins/scanners/vue/src/http.ts
40346
+ import ts3 from "./typescript.cjs";
40347
+
40348
+ // plugins/scanners/http-syntax.ts
40349
+ function wrapper(ts, node) {
40350
+ return ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isNonNullExpression(node) || ts.isTypeAssertionExpression(node);
40351
+ }
40352
+ function unwrapped(ts, node) {
40353
+ let current = node;
40354
+ while (wrapper(ts, current))
40355
+ current = current.expression;
40356
+ return current;
40357
+ }
40358
+ function propertyKey(ts, property) {
40359
+ const name = property.name;
40360
+ if (name === undefined)
40361
+ return;
40362
+ return ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name) ? name.text : undefined;
40363
+ }
40364
+ function ownProperty(ts, object, name) {
40365
+ let found = "absent";
40366
+ for (const property of object.properties) {
40367
+ const key = propertyKey(ts, property);
40368
+ if (key === undefined || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property))
40369
+ return "unknown";
40370
+ if (key === name)
40371
+ found = ts.isPropertyAssignment(property) ? { node: property.initializer } : "unknown";
40372
+ }
40373
+ return found;
40374
+ }
40375
+ function holderOf(ts, node) {
40376
+ let current = node;
40377
+ while (wrapper(ts, current.parent))
40378
+ current = current.parent;
40379
+ return ts.isVariableDeclaration(current.parent) ? current.parent : undefined;
40380
+ }
40381
+ function urlText(ts, node) {
40382
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node))
40383
+ return true;
40384
+ return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken;
40385
+ }
40386
+
40387
+ // plugins/scanners/http-url.ts
40388
+ var pathText2 = /^[A-Za-z0-9\-._~!$&'()*+,;=:@%]+$/;
40389
+ var absolute = /^([A-Za-z][A-Za-z\d+.-]*:)?\/\//;
40390
+ var HOLE = "\x00";
40391
+ var computedPart = { kind: "hole", configured: false };
40392
+ var configuredPart = { kind: "hole", configured: true };
40393
+ var methodToken2 = /^[A-Z][A-Z-]*$/;
40394
+ function methodText(text) {
40395
+ const method = text?.toUpperCase();
40396
+ return method !== undefined && methodToken2.test(method) ? method : undefined;
40397
+ }
40398
+ function requestSegment2(part) {
40399
+ if (part === HOLE)
40400
+ return { kind: "dynamic" };
40401
+ if (part.includes(HOLE) || !pathText2.test(part))
40402
+ return { kind: "unknown" };
40403
+ return { kind: "literal", value: part };
40404
+ }
40405
+ function segmentsOf(parts) {
40406
+ const url = parts.map((part) => part.kind === "text" ? part.text : HOLE).join("");
40407
+ const end = Math.min(...["?", "#"].map((mark) => url.includes(mark) ? url.indexOf(mark) : url.length));
40408
+ return url.slice(0, end).split("/").filter((part) => part !== "").map(requestSegment2);
40409
+ }
40410
+ function afterAuthority(text) {
40411
+ const slash = text.indexOf("/", text.indexOf("//") + 2);
40412
+ return slash < 0 ? "" : text.slice(slash);
40413
+ }
40414
+ function joinedText(input) {
40415
+ const parts = [];
40416
+ for (const part of input) {
40417
+ const last = parts.at(-1);
40418
+ if (part.kind === "text" && last?.kind === "text")
40419
+ parts[parts.length - 1] = { kind: "text", text: last.text + part.text };
40420
+ else if (part.kind === "hole" || part.text !== "")
40421
+ parts.push(part);
40422
+ }
40423
+ return parts;
40424
+ }
40425
+ function requestUrl(input) {
40426
+ const parts = joinedText(input);
40427
+ const [first, ...rest] = parts;
40428
+ if (first === undefined)
40429
+ return { path: [{ kind: "unknown" }] };
40430
+ if (first.kind === "hole") {
40431
+ const path = segmentsOf(rest);
40432
+ const next = rest[0];
40433
+ const separated = next === undefined || next.kind === "text" && next.text.startsWith("/");
40434
+ return first.configured && separated ? { configured: true, path } : { path: [{ kind: "unknown" }, ...path] };
40435
+ }
40436
+ if (absolute.test(first.text)) {
40437
+ return { path: [{ kind: "unknown" }, ...segmentsOf([{ kind: "text", text: afterAuthority(first.text) }, ...rest])] };
40438
+ }
40439
+ if (!first.text.startsWith("/"))
40440
+ return { path: [{ kind: "unknown" }, ...segmentsOf(parts)] };
40441
+ return { path: segmentsOf(parts) };
40442
+ }
40443
+ function joinBase(base, url) {
40444
+ const stated = joinedText(base);
40445
+ const path = joinedText(url);
40446
+ const [first] = path;
40447
+ if (stated.length === 0)
40448
+ return path;
40449
+ if (first === undefined)
40450
+ return stated;
40451
+ if (first.kind === "hole")
40452
+ return [computedPart, ...path.slice(1)];
40453
+ return absolute.test(first.text) ? path : [...stated, { kind: "text", text: "/" }, ...path];
40454
+ }
40455
+ function withBase(base, url) {
40456
+ const stated = joinedText(base);
40457
+ const path = joinedText(url);
40458
+ const [only] = stated;
40459
+ const [first, ...rest] = path;
40460
+ if (stated.length !== 1 || only?.kind !== "text" || first?.kind !== "text" || absolute.test(first.text)) {
40461
+ return joinBase(stated, path);
40462
+ }
40463
+ const prefix = only.text.replace(/\/$/, "");
40464
+ if (first.text.startsWith(prefix)) {
40465
+ const next = first.text[prefix.length];
40466
+ if (next === undefined)
40467
+ return rest.length === 0 ? path : [computedPart, ...rest];
40468
+ return next === "/" || next === "?" ? path : joinBase(stated, path);
40469
+ }
40470
+ return prefix.startsWith(first.text) && rest.length > 0 ? [computedPart, ...rest] : joinBase(stated, path);
40471
+ }
40472
+
40473
+ // plugins/scanners/http-uses.ts
40474
+ function patternPart(ts, parent, target) {
40475
+ if (ts.isPropertyAssignment(parent))
40476
+ return parent.initializer === target;
40477
+ return ts.isArrayLiteralExpression(parent) || ts.isObjectLiteralExpression(parent) || ts.isSpreadElement(parent) || ts.isSpreadAssignment(parent) || ts.isShorthandPropertyAssignment(parent) || wrapper(ts, parent);
40478
+ }
40479
+ function assignment(ts, parent, direct) {
40480
+ const operator = parent.operatorToken.kind;
40481
+ if (operator < ts.SyntaxKind.FirstAssignment || operator > ts.SyntaxKind.LastAssignment)
40482
+ return;
40483
+ return operator === ts.SyntaxKind.EqualsToken && direct ? { assigned: parent.right } : {};
40484
+ }
40485
+ function counted(ts, parent) {
40486
+ return (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) && (parent.operator === ts.SyntaxKind.PlusPlusToken || parent.operator === ts.SyntaxKind.MinusMinusToken);
40487
+ }
40488
+ function writeOf(ts, node) {
40489
+ let target = node;
40490
+ while (patternPart(ts, target.parent, target))
40491
+ target = target.parent;
40492
+ const parent = target.parent;
40493
+ const direct = target === node || wrapper(ts, target);
40494
+ if (ts.isBinaryExpression(parent) && parent.left === target)
40495
+ return assignment(ts, parent, direct);
40496
+ if ((ts.isForInStatement(parent) || ts.isForOfStatement(parent)) && parent.initializer === target)
40497
+ return {};
40498
+ return direct && (counted(ts, parent) || ts.isDeleteExpression(parent)) ? {} : undefined;
40499
+ }
40500
+ function literalKey(ts, node) {
40501
+ return ts.isStringLiteral(node) || ts.isNumericLiteral(node) ? node.text : undefined;
40502
+ }
40503
+ function calledThrough(ts, node) {
40504
+ const parent = node.parent;
40505
+ if (ts.isCallExpression(parent) || ts.isNewExpression(parent))
40506
+ return parent.expression === node;
40507
+ return ts.isTaggedTemplateExpression(parent) && parent.tag === node;
40508
+ }
40509
+ function accessKey(ts, parent, object) {
40510
+ if (ts.isPropertyAccessExpression(parent)) {
40511
+ return parent.expression === object && ts.isIdentifier(parent.name) ? parent.name.text : undefined;
40512
+ }
40513
+ if (!ts.isElementAccessExpression(parent) || parent.expression !== object)
40514
+ return;
40515
+ return literalKey(ts, parent.argumentExpression);
40516
+ }
40517
+ function useOf(ts, reference) {
40518
+ let chain = reference;
40519
+ let receiver;
40520
+ const path = [];
40521
+ for (;; ) {
40522
+ const parent = chain.parent;
40523
+ if (wrapper(ts, parent)) {
40524
+ chain = parent;
40525
+ continue;
40526
+ }
40527
+ const key = accessKey(ts, parent, chain);
40528
+ if (key === undefined)
40529
+ break;
40530
+ receiver = chain;
40531
+ path.push(key);
40532
+ chain = parent;
40533
+ }
40534
+ const write = writeOf(ts, chain);
40535
+ if (write !== undefined)
40536
+ return { kind: "write", path, reference, value: chain, ...write };
40537
+ if (receiver !== undefined && calledThrough(ts, chain)) {
40538
+ return { kind: path.length > 1 ? "read" : "escape", path: path.slice(0, -1), reference, value: receiver };
40539
+ }
40540
+ return { kind: path.length > 0 ? "read" : "escape", path, reference, value: chain };
40541
+ }
40542
+ function importedName(ts, node) {
40543
+ const parent = node.parent;
40544
+ if (ts.isImportClause(parent))
40545
+ return parent.name === node;
40546
+ return (ts.isImportSpecifier(parent) || ts.isNamespaceImport(parent) || ts.isImportEqualsDeclaration(parent)) && parent.name === node;
40547
+ }
40548
+ function typePosition(ts, node) {
40549
+ let current = node;
40550
+ while (ts.isQualifiedName(current.parent))
40551
+ current = current.parent;
40552
+ return ts.isTypeQueryNode(current.parent);
40553
+ }
40554
+ function exportStatement(ts, node) {
40555
+ const parent = node.parent;
40556
+ return ts.isExportSpecifier(parent) || ts.isExportAssignment(parent) && parent.expression === node;
40557
+ }
40558
+ function exported(ts, declaration) {
40559
+ const statement = declaration.parent.parent;
40560
+ return statement?.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) === true;
40561
+ }
40562
+ function topLevel(ts, declaration) {
40563
+ const statement = declaration.parent?.parent;
40564
+ return statement?.parent !== undefined && ts.isSourceFile(statement.parent);
40565
+ }
40566
+ function scriptFile(ts, source, names) {
40567
+ if (ts.isExternalModule(source) || /\.[cm]js$/.test(source.fileName))
40568
+ return false;
40569
+ const required = (names.get("require") ?? []).some((node) => ts.isCallExpression(node.parent) && node.parent.expression === node);
40570
+ const exporting = ["module", "exports"].some((name) => (names.get(name) ?? []).some((node) => ts.isPropertyAccessExpression(node.parent) && node.parent.expression === node));
40571
+ return !required && !exporting;
40572
+ }
40573
+ function dynamicBinding(ts, call) {
40574
+ let holder = call;
40575
+ let awaited = false;
40576
+ while (ts.isAwaitExpression(holder.parent) || ts.isParenthesizedExpression(holder.parent)) {
40577
+ awaited ||= ts.isAwaitExpression(holder.parent);
40578
+ holder = holder.parent;
40579
+ }
40580
+ const parent = holder.parent;
40581
+ if (ts.isVariableDeclaration(parent))
40582
+ return awaited && ts.isIdentifier(parent.name) ? parent.name : undefined;
40583
+ const then = ts.isPropertyAccessExpression(parent) && parent.expression === holder && !awaited ? parent.parent : undefined;
40584
+ const callback = then !== undefined && ts.isCallExpression(then) ? then.arguments[0] : undefined;
40585
+ if (callback === undefined || !ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback))
40586
+ return;
40587
+ const [first] = callback.parameters;
40588
+ return first !== undefined && ts.isIdentifier(first.name) ? first.name : undefined;
40589
+ }
40590
+ function requiredModule(ts, node) {
40591
+ if (node !== undefined && ts.isExternalModuleReference(node))
40592
+ return ts.isStringLiteral(node.expression) ? node.expression.text : undefined;
40593
+ if (node === undefined || !ts.isCallExpression(node) || !ts.isIdentifier(node.expression) || node.expression.text !== "require")
40594
+ return;
40595
+ const [specifier] = node.arguments;
40596
+ return specifier !== undefined && ts.isStringLiteral(specifier) ? specifier.text : undefined;
40597
+ }
40598
+ function importModule(ts, statement, name) {
40599
+ return ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) ? { module: statement.moduleSpecifier.text, name } : undefined;
40600
+ }
40601
+ function requiredValue(ts, initializer) {
40602
+ const value = initializer === undefined ? undefined : unwrapped(ts, initializer);
40603
+ const direct = requiredModule(ts, value);
40604
+ if (direct !== undefined)
40605
+ return { module: direct, name: "default" };
40606
+ if (value === undefined || !ts.isPropertyAccessExpression(value) || !ts.isIdentifier(value.name))
40607
+ return;
40608
+ const module = requiredModule(ts, unwrapped(ts, value.expression));
40609
+ return module === undefined ? undefined : { module, name: value.name.text };
40610
+ }
40611
+ function boundExport(ts, declaration) {
40612
+ if (ts.isImportClause(declaration))
40613
+ return importModule(ts, declaration.parent, "default");
40614
+ if (ts.isNamespaceImport(declaration))
40615
+ return importModule(ts, declaration.parent.parent, "*");
40616
+ if (ts.isImportSpecifier(declaration)) {
40617
+ return importModule(ts, declaration.parent.parent.parent, (declaration.propertyName ?? declaration.name).text);
40618
+ }
40619
+ if (ts.isImportEqualsDeclaration(declaration))
40620
+ return requiredValue(ts, declaration.moduleReference);
40621
+ if (ts.isVariableDeclaration(declaration))
40622
+ return requiredValue(ts, declaration.initializer);
40623
+ if (!ts.isBindingElement(declaration))
40624
+ return;
40625
+ const variable = declaration.parent.parent;
40626
+ const module = ts.isVariableDeclaration(variable) ? requiredModule(ts, variable.initializer) : undefined;
40627
+ const key = declaration.propertyName ?? declaration.name;
40628
+ return module !== undefined && key !== undefined && ts.isIdentifier(key) ? { module, name: key.text } : undefined;
40629
+ }
40630
+ function defaultImport(ts, name) {
40631
+ const declaration = name.parent;
40632
+ const origin = declaration.name === name ? boundExport(ts, declaration) : undefined;
40633
+ return origin?.name === "default" ? { declaration, module: origin.module } : undefined;
40634
+ }
40635
+ function indexName(ts, index, node) {
40636
+ const same = index.names.get(node.text);
40637
+ if (same === undefined)
40638
+ index.names.set(node.text, [node]);
40639
+ else
40640
+ same.push(node);
40641
+ if (importedName(ts, node))
40642
+ index.imported.add(node.text);
40643
+ const imported = defaultImport(ts, node);
40644
+ if (imported !== undefined)
40645
+ index.defaults.set(imported.module, [...index.defaults.get(imported.module) ?? [], imported.declaration]);
40646
+ }
40647
+ function indexSources(ts, sources) {
40648
+ const index = { names: new Map, imported: new Set, dynamic: [], defaults: new Map };
40649
+ const visit = (node) => {
40650
+ if (ts.isIdentifier(node))
40651
+ indexName(ts, index, node);
40652
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
40653
+ const binding = dynamicBinding(ts, node);
40654
+ index.dynamic.push({ call: node, ...binding === undefined ? {} : { binding } });
40655
+ }
40656
+ ts.forEachChild(node, visit);
40657
+ };
40658
+ for (const source of sources)
40659
+ visit(source);
40660
+ return index;
40661
+ }
40662
+ function startsWith(path, prefix) {
40663
+ return prefix.length <= path.length && prefix.every((name, index) => path[index] === name);
40664
+ }
40665
+ function useAt(ts, node) {
40666
+ const parent = node.parent;
40667
+ if (ts.isVariableDeclaration(parent) && parent.name === node)
40668
+ return { kind: "write", path: [], reference: node, value: node };
40669
+ if (exportStatement(ts, node))
40670
+ return { kind: "export", path: [], reference: node, value: node };
40671
+ const accessed = ts.isPropertyAccessExpression(parent) && parent.name === node;
40672
+ return useOf(ts, accessed ? parent : node);
40673
+ }
40674
+ function objectUse(ts, node) {
40675
+ const parent = node.parent;
40676
+ const named = ts.isPropertyAccessExpression(parent) && parent.expression === node && ts.isIdentifier(parent.name);
40677
+ const declaring = (ts.isVariableDeclaration(parent) || ts.isParameter(parent)) && parent.name === node;
40678
+ return !named && !declaring && !importedName(ts, node) && !typePosition(ts, node);
40679
+ }
40680
+ function settingEffect(use, path, keys) {
40681
+ if (use.path.length === 0 || use.kind === "export")
40682
+ return { kind: "none" };
40683
+ if (startsWith(path, use.path))
40684
+ return { kind: "changes" };
40685
+ const key = startsWith(use.path, path) ? use.path[path.length] : undefined;
40686
+ if (key === undefined || !keys.includes(key))
40687
+ return { kind: "none" };
40688
+ if (use.assigned !== undefined && use.path.length === path.length + 1)
40689
+ return { kind: "assigns", key, value: use.assigned };
40690
+ return { kind: "changes" };
40691
+ }
40692
+
40693
+ // plugins/scanners/http-bindings.ts
40694
+ function bindingUses(ts, checker, sources, options) {
40695
+ let index;
40696
+ const indexed = () => {
40697
+ index ??= indexSources(ts, sources);
40698
+ return index;
40699
+ };
40700
+ const uses = new Map;
40701
+ const symbols = new Map;
40702
+ const shorthand = (node) => ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node;
40703
+ const resolveAll = (names) => {
40704
+ const pending = names.filter((node) => !symbols.has(node) && !shorthand(node));
40705
+ const batch = pending.length === 0 ? Promise.resolve([]) : checker.symbolsAt(pending);
40706
+ for (const [position, node] of pending.entries())
40707
+ symbols.set(node, batch.then((found) => found[position]));
40708
+ };
40709
+ const symbolOf = (node) => {
40710
+ let found = symbols.get(node);
40711
+ if (found === undefined) {
40712
+ found = shorthand(node) ? checker.shorthandValue(node.parent) : checker.symbolsAt([node]).then(([symbol]) => symbol);
40713
+ symbols.set(node, found);
40714
+ }
40715
+ return found;
40716
+ };
40717
+ const refersTo = async (node, declaration) => {
40718
+ const symbol = await symbolOf(node);
40719
+ if (symbol === undefined || node.parent === declaration)
40720
+ return false;
40721
+ if (await checker.declaration(symbol) === declaration)
40722
+ return true;
40723
+ if (importedName(ts, node))
40724
+ return false;
40725
+ return await checker.declaration(await checker.aliased(symbol)) === declaration;
40726
+ };
40727
+ const usesOf = (declaration) => {
40728
+ let found = uses.get(declaration);
40729
+ if (found !== undefined)
40730
+ return found;
40731
+ found = (async () => {
40732
+ const { names, imported } = indexed();
40733
+ const name = declaration.name !== undefined && ts.isIdentifier(declaration.name) ? declaration.name.text : undefined;
40734
+ const texts = name === undefined ? [] : [name, "default", ...imported];
40735
+ const candidates = [...new Set(texts.flatMap((text) => names.get(text) ?? []))].filter((node) => !typePosition(ts, node));
40736
+ resolveAll(candidates);
40737
+ const referring = await Promise.all(candidates.map((node) => refersTo(node, declaration)));
40738
+ return candidates.filter((_, position) => referring[position]).map((node) => useAt(ts, node));
40739
+ })();
40740
+ uses.set(declaration, found);
40741
+ return found;
40742
+ };
40743
+ const dynamicEscapes = async ({ binding }) => {
40744
+ if (binding === undefined)
40745
+ return true;
40746
+ const bound = await symbolOf(binding);
40747
+ const references = indexed().names.get(binding.text) ?? [];
40748
+ resolveAll(references);
40749
+ for (const node of references) {
40750
+ const symbol = await symbolOf(node);
40751
+ if (bound !== undefined && symbol !== undefined && checker.same(symbol, bound) && objectUse(ts, node))
40752
+ return true;
40753
+ }
40754
+ return false;
40755
+ };
40756
+ const moduleOf = async (node) => {
40757
+ const symbol = await symbolOf(node);
40758
+ const module = symbol === undefined ? undefined : await checker.aliased(symbol);
40759
+ return module !== undefined && checker.module(module) ? module : undefined;
40760
+ };
40761
+ const loadedModule = async (entry) => {
40762
+ const [argument] = entry.call.arguments;
40763
+ const [module] = argument === undefined ? [] : await checker.symbolsAt([argument]);
40764
+ return module !== undefined && await dynamicEscapes(entry) ? module : undefined;
40765
+ };
40766
+ const escapedModules = async () => {
40767
+ const { names, imported, dynamic } = indexed();
40768
+ const importedNames = [...imported].flatMap((text) => names.get(text) ?? []).filter((node) => objectUse(ts, node));
40769
+ resolveAll(importedNames);
40770
+ const found = await Promise.all([...importedNames.map(moduleOf), ...dynamic.map(loadedModule)]);
40771
+ const modules = [];
40772
+ for (const module of found) {
40773
+ if (module !== undefined && !modules.some((known) => checker.same(known, module)))
40774
+ modules.push(module);
40775
+ }
40776
+ return modules;
40777
+ };
40778
+ let reachable;
40779
+ const escapedExports = () => {
40780
+ reachable ??= (async () => {
40781
+ const exports = (await Promise.all((await escapedModules()).map((module) => checker.exportsOf(module)))).flat();
40782
+ const declarations = await Promise.all(exports.map(async (symbol) => checker.declaration(await checker.aliased(symbol))));
40783
+ return new Set(declarations.filter((declaration) => declaration !== undefined));
40784
+ })();
40785
+ return reachable;
40786
+ };
40787
+ const script = () => options.fileAlone && sources.some((source) => scriptFile(ts, source, indexed().names));
40788
+ const leaves = async (declaration) => {
40789
+ if (!options.fileAlone)
40790
+ return (await escapedExports()).has(declaration);
40791
+ return exported(ts, declaration) || topLevel(ts, declaration) && script();
40792
+ };
40793
+ const readChanges = async (use) => use.kind !== "read" || !await checker.primitiveAt(use.value);
40794
+ const changes = async (use, path, reference) => {
40795
+ if (use.reference === reference || !startsWith(path, use.path))
40796
+ return false;
40797
+ return use.kind === "export" ? options.fileAlone : readChanges(use);
40798
+ };
40799
+ const settingAt = async (settings, use, path, keys) => {
40800
+ const effect = settingEffect(use, path, keys);
40801
+ if (effect.kind === "assigns")
40802
+ settings.assigned.set(effect.key, [...settings.assigned.get(effect.key) ?? [], effect.value]);
40803
+ else if (effect.kind === "changes")
40804
+ settings.changed ||= await readChanges(use);
40805
+ };
40806
+ return {
40807
+ async unchanged(declaration, path, reference) {
40808
+ if (await leaves(declaration))
40809
+ return false;
40810
+ for (const use of await usesOf(declaration))
40811
+ if (await changes(use, path, reference))
40812
+ return false;
40813
+ return true;
40814
+ },
40815
+ async reassigned(declaration) {
40816
+ if (options.fileAlone && topLevel(ts, declaration) && script())
40817
+ return true;
40818
+ return (await usesOf(declaration)).some((use) => use.kind === "write" && use.path.length === 0);
40819
+ },
40820
+ async settings(declaration, path, keys) {
40821
+ const settings = { assigned: new Map, changed: await leaves(declaration) };
40822
+ for (const use of await usesOf(declaration))
40823
+ await settingAt(settings, use, path, keys);
40824
+ return settings;
40825
+ },
40826
+ defaultImports: (module) => indexed().defaults.get(module) ?? [],
40827
+ references: async (declaration) => (await usesOf(declaration)).map((use) => use.reference),
40828
+ named: (text) => indexed().names.get(text) ?? [],
40829
+ fileAlone: options.fileAlone
40830
+ };
40831
+ }
40832
+
40833
+ // plugins/scanners/http-values.ts
40834
+ function urlContext(ts, checker, sources, fileAlone = false) {
40835
+ return { ts, checker, bindings: bindingUses(ts, checker, sources, { fileAlone }) };
40836
+ }
40837
+ var MAX_DEPTH = 8;
40838
+ function declarationFile(node) {
40839
+ return /\.d\.[cm]?ts$/.test(node.getSourceFile().fileName);
40840
+ }
40841
+ async function declarationOf(context, node) {
40842
+ const { checker } = context;
40843
+ const [symbol] = await checker.symbolsAt([node]);
40844
+ return symbol === undefined ? undefined : checker.declaration(await checker.aliased(symbol));
40845
+ }
40846
+ var moduleNamesByFile = new WeakMap;
40847
+ function moduleNames(ts, source) {
40848
+ let names = moduleNamesByFile.get(source);
40849
+ if (names !== undefined)
40850
+ return names;
40851
+ const found = new Set;
40852
+ const visit = (node) => {
40853
+ const name = node.name;
40854
+ if (name !== undefined && ts.isIdentifier(name) && boundExport(ts, node) !== undefined)
40855
+ found.add(name.text);
40856
+ ts.forEachChild(node, visit);
40857
+ };
40858
+ visit(source);
40859
+ names = found;
40860
+ moduleNamesByFile.set(source, names);
40861
+ return names;
40862
+ }
40863
+ async function importOrigin(context, node) {
40864
+ const { ts, checker } = context;
40865
+ const required = requiredModule(ts, node);
40866
+ if (required !== undefined)
40867
+ return { module: required, name: "default" };
40868
+ if (!ts.isIdentifier(node) || !moduleNames(ts, node.getSourceFile()).has(node.text))
40869
+ return;
40870
+ const [symbol] = await checker.symbolsAt([node]);
40871
+ const declaration = symbol === undefined ? undefined : await checker.declaration(symbol);
40872
+ const origin = declaration === undefined ? undefined : boundExport(ts, declaration);
40873
+ if (origin === undefined || !ts.isVariableDeclaration(declaration) && !ts.isBindingElement(declaration))
40874
+ return origin;
40875
+ return await unassigned(context, declaration) ? origin : undefined;
40876
+ }
40877
+ async function runtimeGlobal(context, node) {
40878
+ if (await importOrigin(context, node) !== undefined)
40879
+ return false;
40880
+ const declaration = await declarationOf(context, node);
40881
+ return declaration === undefined || declarationFile(declaration);
40882
+ }
40883
+ function ambient(ts, declaration) {
40884
+ const flag = ts.NodeFlags.Ambient ?? 0;
40885
+ return ((declaration.flags ?? 0) & flag) !== 0;
40886
+ }
40887
+ async function unassigned(context, declaration) {
40888
+ const { ts, checker } = context;
40889
+ const list = ts.isVariableDeclaration(declaration) ? declaration.parent : declaration.parent.parent.parent;
40890
+ if (((list.flags ?? 0) & ts.NodeFlags.Const) !== 0)
40891
+ return true;
40892
+ if (declaration.name === undefined)
40893
+ return false;
40894
+ const [symbol] = await checker.symbolsAt([declaration.name]);
40895
+ return symbol !== undefined && checker.declarationCount(symbol) === 1 && !await context.bindings.reassigned(declaration);
40896
+ }
40897
+ async function variableHeld(context, name, path, depth) {
40898
+ const { ts } = context;
40899
+ const declaration = await declarationOf(context, name);
40900
+ if (declaration === undefined || declarationFile(declaration) || ambient(ts, declaration))
40901
+ return "unseen";
40902
+ if (path.length === 0 && ts.isFunctionDeclaration(declaration))
40903
+ return { node: declaration };
40904
+ if (!ts.isVariableDeclaration(declaration) || declaration.initializer === undefined)
40905
+ return "unknown";
40906
+ if (!ts.isIdentifier(declaration.name) || !await unassigned(context, declaration))
40907
+ return "unknown";
40908
+ if (path.length > 0 && !await context.bindings.unchanged(declaration, path, name))
40909
+ return "unknown";
40910
+ return held(context, declaration.initializer, path, depth + 1);
40911
+ }
40912
+ async function extendsClass(context, klass, base, depth = 0) {
40913
+ const { ts } = context;
40914
+ if (!ts.isClassLike(klass) || depth > MAX_DEPTH)
40915
+ return false;
40916
+ const clauses = klass.heritageClauses ?? [];
40917
+ const clause = Array.from(clauses).find((entry) => entry.token === ts.SyntaxKind.ExtendsKeyword);
40918
+ const parent = clause?.types[0];
40919
+ const declaration = parent === undefined ? undefined : await declarationOf(context, parent.expression);
40920
+ return declaration !== undefined && (declaration === base || extendsClass(context, declaration, base, depth + 1));
40921
+ }
40922
+ async function assignmentAt(context, node, same, declaration) {
40923
+ const { ts } = context;
40924
+ const parent = node.parent;
40925
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
40926
+ const use = same ? useAt(ts, node) : undefined;
40927
+ return use?.kind === "write" ? { value: use.path.length === 0 ? use.assigned : undefined } : undefined;
40928
+ }
40929
+ if (!ts.isPropertyDeclaration(parent) || parent.name !== node || declaration === undefined || parent === declaration)
40930
+ return;
40931
+ return await extendsClass(context, parent.parent, declaration.parent) ? { value: parent.initializer } : undefined;
40932
+ }
40933
+ async function fieldAssignments(context, name, declaration) {
40934
+ const { ts, checker } = context;
40935
+ const [symbol] = await checker.symbolsAt([name]);
40936
+ const initialized = declaration !== undefined && (ts.isPropertyDeclaration(declaration) || ts.isPropertyAssignment(declaration)) ? declaration.initializer : undefined;
40937
+ const assignments = initialized === undefined ? [] : [initialized];
40938
+ const others = context.bindings.named(name.text).filter((node) => node !== name);
40939
+ const symbols = await checker.symbolsAt(others);
40940
+ for (const [position, node] of others.entries()) {
40941
+ const found = symbols[position];
40942
+ const same = found !== undefined && symbol !== undefined && checker.same(found, symbol);
40943
+ const assignment = await assignmentAt(context, node, same, declaration);
40944
+ if (assignment !== undefined)
40945
+ assignments.push(assignment.value);
40946
+ }
40947
+ return assignments;
40948
+ }
40949
+ async function fieldHeld(context, access, path, depth) {
40950
+ const { ts, checker } = context;
40951
+ const [symbol] = await checker.symbolsAt([access.name]);
40952
+ const declaration = symbol === undefined ? undefined : await checker.declaration(symbol);
40953
+ const assignments = await fieldAssignments(context, access.name, declaration);
40954
+ const injected = declaration !== undefined && ts.isParameter(declaration);
40955
+ if (assignments.length === 0)
40956
+ return "unseen";
40957
+ const [only] = assignments;
40958
+ if (injected || assignments.length > 1 || only === undefined || path.length > 0 || context.bindings.fileAlone)
40959
+ return "unknown";
40960
+ return held(context, only, path, depth + 1);
40961
+ }
40962
+ async function held(context, node, path, depth) {
40963
+ const { ts } = context;
40964
+ if (depth > MAX_DEPTH)
40965
+ return "unknown";
40966
+ const value = unwrapped(ts, node);
40967
+ const [name, ...rest] = path;
40968
+ if (ts.isObjectLiteralExpression(value) && name !== undefined) {
40969
+ const property = ownProperty(ts, value, name);
40970
+ if (typeof property !== "string")
40971
+ return held(context, property.node, rest, depth + 1);
40972
+ return property === "absent" && rest.length === 0 ? "absent" : "unknown";
40973
+ }
40974
+ if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.name)) {
40975
+ if (value.expression.kind === ts.SyntaxKind.ThisKeyword)
40976
+ return fieldHeld(context, value, path, depth);
40977
+ return held(context, value.expression, [value.name.text, ...path], depth + 1);
40978
+ }
40979
+ if (ts.isIdentifier(value))
40980
+ return variableHeld(context, value, path, depth);
40981
+ if (name === undefined)
40982
+ return { node: value };
40983
+ return ts.isMetaProperty(value) ? "unseen" : "unknown";
40984
+ }
40985
+ function heldAt(context, node, ...path) {
40986
+ return held(context, node, path, 0);
40987
+ }
40988
+ async function heldParts(context, value) {
40989
+ if (value === "unseen")
40990
+ return [configuredPart];
40991
+ return typeof value === "string" ? [computedPart] : urlParts(context, value.node);
40992
+ }
40993
+ async function urlParts(context, node, depth = 0) {
40994
+ const { ts } = context;
40995
+ if (depth > MAX_DEPTH)
40996
+ return [computedPart];
40997
+ const value = unwrapped(ts, node);
40998
+ if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value))
40999
+ return [{ kind: "text", text: value.text }];
41000
+ if (ts.isTemplateExpression(value)) {
41001
+ const parts = [{ kind: "text", text: value.head.text }];
41002
+ for (const span of value.templateSpans) {
41003
+ parts.push(...await urlParts(context, span.expression, depth + 1), { kind: "text", text: span.literal.text });
41004
+ }
41005
+ return parts;
41006
+ }
41007
+ if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.PlusToken) {
41008
+ return [...await urlParts(context, value.left, depth + 1), ...await urlParts(context, value.right, depth + 1)];
41009
+ }
41010
+ if (!ts.isIdentifier(value) && !ts.isPropertyAccessExpression(value))
41011
+ return [computedPart];
41012
+ const resolved = await held(context, value, [], depth + 1);
41013
+ return typeof resolved === "string" ? heldParts(context, resolved) : urlParts(context, resolved.node, depth + 1);
41014
+ }
41015
+ async function literalText(context, node) {
41016
+ if (node === undefined)
41017
+ return;
41018
+ const [first, ...rest] = await urlParts(context, node);
41019
+ return rest.length === 0 && first?.kind === "text" ? first.text : undefined;
41020
+ }
41021
+ async function methodName(context, node) {
41022
+ return methodText(await literalText(context, node));
41023
+ }
41024
+
41025
+ // plugins/scanners/http-clients.ts
41026
+ async function declaredMethod(context, options, fallback) {
41027
+ if (options === undefined)
41028
+ return fallback;
41029
+ const declared = await heldAt(context, options, "method");
41030
+ if (declared === "absent")
41031
+ return fallback;
41032
+ return typeof declared === "string" ? undefined : methodName(context, declared.node);
41033
+ }
41034
+ async function optionsBase(context, config, fallback) {
41035
+ if (config === undefined)
41036
+ return fallback;
41037
+ const base = await heldAt(context, config, "baseURL");
41038
+ return base === "absent" ? fallback : heldParts(context, base);
41039
+ }
41040
+ async function runtimeFetch(context, callee) {
41041
+ if (!context.ts.isIdentifier(callee) || callee.text !== "fetch")
41042
+ return;
41043
+ const origin = await importOrigin(context, callee);
41044
+ const fetch = origin === undefined ? await runtimeGlobal(context, callee) : origin.module === "node-fetch" && origin.name === "default";
41045
+ return fetch ? "fetch" : undefined;
41046
+ }
41047
+ async function fetchRequest(context, call, client) {
41048
+ const [url, options] = call.arguments;
41049
+ const kind = url === undefined ? undefined : await client(call.expression);
41050
+ if (url === undefined || kind === undefined)
41051
+ return;
41052
+ const input = await heldAt(context, url);
41053
+ const method = input === "unseen" || typeof input === "object" && urlText(context.ts, input.node) ? await declaredMethod(context, options, "GET") : undefined;
41054
+ const parts = await urlParts(context, url);
41055
+ const target = kind === "ofetch" ? withBase(await optionsBase(context, options, []), parts) : parts;
41056
+ return { ...method === undefined ? {} : { method }, ...requestUrl(target) };
41057
+ }
41058
+ async function withDefaults(context, client, holders) {
41059
+ const found = await Promise.all(holders.map((holder) => context.bindings.settings(holder, ["defaults"], ["baseURL", "method"])));
41060
+ const bases = found.flatMap((settings) => settings.assigned.get("baseURL") ?? []);
41061
+ const methods = found.flatMap((settings) => settings.assigned.get("method") ?? []);
41062
+ if (found.some((settings) => settings.changed) || bases.length > 1 || methods.length > 1)
41063
+ return { base: [computedPart], method: undefined };
41064
+ return {
41065
+ base: bases[0] === undefined ? client.base : await urlParts(context, bases[0]),
41066
+ method: methods[0] === undefined ? client.method : methodText(await literalText(context, methods[0]))
41067
+ };
41068
+ }
41069
+ function defaultClient(context) {
41070
+ return withDefaults(context, { base: [], method: "GET" }, context.bindings.defaultImports("axios"));
41071
+ }
41072
+ async function sent(context, client, url, method, config) {
41073
+ const effective = method ?? await declaredMethod(context, config, client.method);
41074
+ const base = await optionsBase(context, config, client.base);
41075
+ return { ...effective === undefined ? {} : { method: effective }, ...requestUrl(joinBase(base, url)) };
41076
+ }
41077
+ async function configRequest(context, client, config) {
41078
+ const url = config === undefined ? "absent" : await heldAt(context, config, "url");
41079
+ return url === "absent" ? undefined : sent(context, client, await heldParts(context, url), undefined, config);
41080
+ }
41081
+ async function holdsObject(context, node) {
41082
+ const value = await heldAt(context, node);
41083
+ return typeof value === "object" && context.ts.isObjectLiteralExpression(value.node);
41084
+ }
41085
+ var SHORTHAND = new Map([
41086
+ ["get", "GET"],
41087
+ ["post", "POST"],
41088
+ ["put", "PUT"],
41089
+ ["patch", "PATCH"],
41090
+ ["delete", "DELETE"],
41091
+ ["head", "HEAD"],
41092
+ ["options", "OPTIONS"]
41093
+ ]);
41094
+ var WITH_BODY = new Set(["post", "put", "patch"]);
41095
+ async function isAxios(context, node) {
41096
+ const origin = await importOrigin(context, node);
41097
+ return origin?.module === "axios" && origin.name === "default";
41098
+ }
41099
+ async function createdClient(context, node) {
41100
+ const { ts } = context;
41101
+ const created = await heldAt(context, node);
41102
+ const call = typeof created === "object" && ts.isCallExpression(created.node) ? created.node : undefined;
41103
+ const callee = call?.expression;
41104
+ if (call === undefined || callee === undefined || !ts.isPropertyAccessExpression(callee) || !ts.isIdentifier(callee.name))
41105
+ return;
41106
+ if (callee.name.text !== "create" || !await isAxios(context, callee.expression))
41107
+ return;
41108
+ const parent = await defaultClient(context);
41109
+ const inherited = { base: parent.base.length === 0 ? [] : [computedPart], method: parent.method === "GET" ? "GET" : undefined };
41110
+ const [config] = call.arguments;
41111
+ const client = { base: await optionsBase(context, config, inherited.base), method: await declaredMethod(context, config, inherited.method) };
41112
+ const holders = [await declarationOf(context, node), holderOf(ts, call)].filter((holder) => holder !== undefined);
41113
+ return withDefaults(context, client, [...new Set(holders)]);
41114
+ }
41115
+ async function axiosClient(context, node) {
41116
+ if (!context.ts.isIdentifier(node))
41117
+ return;
41118
+ return await isAxios(context, node) ? defaultClient(context) : createdClient(context, node);
41119
+ }
41120
+ async function axiosRequest(context, call) {
41121
+ const { ts } = context;
41122
+ const callee = call.expression;
41123
+ const [first, second, third] = call.arguments;
41124
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
41125
+ const client = await axiosClient(context, callee.expression);
41126
+ if (client === undefined)
41127
+ return;
41128
+ const member = callee.name.text;
41129
+ const method = SHORTHAND.get(member);
41130
+ if (method !== undefined && first !== undefined) {
41131
+ return sent(context, client, await urlParts(context, first), method, WITH_BODY.has(member) ? third : second);
41132
+ }
41133
+ return member === "request" ? configRequest(context, client, first) : undefined;
41134
+ }
41135
+ const client = await axiosClient(context, callee);
41136
+ if (client === undefined || first === undefined)
41137
+ return;
41138
+ if (await holdsObject(context, first))
41139
+ return configRequest(context, client, first);
41140
+ return sent(context, client, await urlParts(context, first), undefined, second);
41141
+ }
41142
+
41143
+ // plugins/scanners/http-checker.ts
41144
+ function primitive(ts, type) {
41145
+ if (type.isUnion())
41146
+ return (type.types ?? []).every((member) => primitive(ts, member));
41147
+ const flags = ts.TypeFlags;
41148
+ const mask = flags.StringLike | flags.NumberLike | flags.BigIntLike | flags.BooleanLike | flags.EnumLike | flags.ESSymbolLike | flags.VoidLike | flags.Null;
41149
+ return (type.flags & mask) !== 0;
41150
+ }
41151
+ function classicChecker(ts, checker) {
41152
+ const symbol = (handle) => handle;
41153
+ return {
41154
+ symbolsAt: async (nodes) => nodes.map((node) => checker.getSymbolAtLocation(node)),
41155
+ shorthandValue: async (property) => checker.getShorthandAssignmentValueSymbol(property),
41156
+ aliased: async (handle) => symbol(handle).flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol(handle)) : handle,
41157
+ module: (handle) => (symbol(handle).flags & ts.SymbolFlags.ValueModule) !== 0,
41158
+ declaration: async (handle) => symbol(handle).valueDeclaration ?? symbol(handle).declarations?.[0],
41159
+ declarationCount: (handle) => symbol(handle).declarations?.length ?? 0,
41160
+ exportsOf: async (handle) => checker.getExportsOfModule(symbol(handle)),
41161
+ primitiveAt: async (node) => primitive(ts, checker.getTypeAtLocation(node)),
41162
+ same: (left, right) => left === right
41163
+ };
41164
+ }
41165
+
41166
+ // plugins/scanners/vue/src/server-routes.ts
41167
+ var SERVER_ROUTE = /^server\/((?:api|routes)\/.+)\.(?:[cm]?[jt]s|[jt]sx)$/;
41168
+ var GROUP = /\([^(/]+\)\//g;
41169
+ var SUFFIX = /(?:\.(connect|delete|get|head|options|patch|post|put|trace))?(?:\.(?:dev|prod|prerender))?$/;
41170
+ function named(text) {
41171
+ return text !== "" && pathText2.test(text) ? text : "*";
41172
+ }
41173
+ function routeSegment(part, last) {
41174
+ const registered = part.replace(/\[\.{3}]/g, "**").replace(/\[\.{3}(\w+)]/g, "**:$1").replace(/\[([^/\]]+)]/g, ":$1");
41175
+ if (registered.startsWith("**"))
41176
+ return last ? { kind: "catch-all", name: named(registered.slice(3)) } : undefined;
41177
+ if (registered.startsWith(":"))
41178
+ return { kind: "parameter", name: named(registered.slice(1)) };
41179
+ return pathText2.test(registered) ? { kind: "literal", value: registered } : undefined;
41180
+ }
41181
+ function serverRoute(file) {
41182
+ const match = SERVER_ROUTE.exec(file);
41183
+ if (match === null)
41184
+ return;
41185
+ const grouped = match[1].replace(GROUP, "");
41186
+ const suffix = SUFFIX.exec(grouped);
41187
+ const route = grouped.slice(0, suffix.index);
41188
+ const parts = (route.startsWith("routes/") ? route.slice("routes/".length) : route).split("/");
41189
+ const segments = parts.at(-1) === "index" ? parts.slice(0, -1) : parts;
41190
+ const path = [];
41191
+ for (const [index, part] of segments.entries()) {
41192
+ const segment = routeSegment(part, index === segments.length - 1);
41193
+ if (segment === undefined) {
41194
+ path.push({ kind: "catch-all", name: "*", optional: true, constrained: true });
41195
+ break;
41196
+ }
41197
+ path.push(segment);
41198
+ }
41199
+ return { method: suffix[1]?.toUpperCase() ?? "*", path };
41200
+ }
41201
+
41202
+ // plugins/scanners/vue/src/http.ts
41203
+ var NUXT_CLIENTS = new Set(["$fetch", "useFetch"]);
41204
+ function projectSource(projectRoot, fileName) {
41205
+ const file = relative(projectRoot, fileName);
41206
+ return !file.startsWith("../") && !file.includes("node_modules/");
41207
+ }
41208
+ async function vueClient(context, projectRoot, callee) {
41209
+ if (!ts3.isIdentifier(callee) || !NUXT_CLIENTS.has(callee.text))
41210
+ return runtimeFetch(context, callee);
41211
+ const declaration = await declarationOf(context, callee);
41212
+ return declaration !== undefined && !projectSource(projectRoot, declaration.getSourceFile().fileName) ? "ofetch" : undefined;
41213
+ }
41214
+ function isOperation(node) {
41215
+ if (ts3.isFunctionDeclaration(node) || ts3.isMethodDeclaration(node))
41216
+ return node.body !== undefined;
41217
+ return ts3.isArrowFunction(node) || ts3.isFunctionExpression(node);
41218
+ }
41219
+ async function localFunction(context, node, source) {
41220
+ if (isOperation(node))
41221
+ return node;
41222
+ if (!ts3.isIdentifier(node))
41223
+ return;
41224
+ const held = await heldAt(context, node);
41225
+ const value = typeof held === "object" ? held.node : undefined;
41226
+ return value !== undefined && isOperation(value) && value.getSourceFile() === source ? value : undefined;
41227
+ }
41228
+ function isDefaultFunction(statement) {
41229
+ if (!ts3.isFunctionDeclaration(statement) || !ts3.canHaveModifiers(statement))
41230
+ return false;
41231
+ return (ts3.getModifiers(statement) ?? []).some((modifier) => modifier.kind === ts3.SyntaxKind.DefaultKeyword);
41232
+ }
41233
+ async function routeHandler(context, source) {
41234
+ for (const statement of source.statements) {
41235
+ if (isDefaultFunction(statement))
41236
+ return isOperation(statement) ? statement : undefined;
41237
+ if (!ts3.isExportAssignment(statement) || statement.isExportEquals === true)
41238
+ continue;
41239
+ const value = statement.expression;
41240
+ const handler = ts3.isCallExpression(value) ? value.arguments[0] : value;
41241
+ return handler === undefined ? undefined : localFunction(context, handler, source);
41242
+ }
41243
+ return;
41244
+ }
41245
+ function calls(source) {
41246
+ const found = [];
41247
+ const visit = (node) => {
41248
+ if (ts3.isCallExpression(node))
41249
+ found.push(node);
41250
+ ts3.forEachChild(node, visit);
41251
+ };
41252
+ visit(source);
41253
+ return found;
41254
+ }
41255
+ async function fileRequests(context, source, file, input) {
41256
+ const requests = [];
41257
+ for (const call of calls(source)) {
41258
+ const fact = await fetchRequest(context, call, (callee) => vueClient(context, input.projectRoot, callee)) ?? await axiosRequest(context, call);
41259
+ if (fact === undefined)
41260
+ continue;
41261
+ const caller = enclosingOperation(call);
41262
+ const operation = (caller === undefined ? undefined : input.operationId(caller)) ?? input.moduleOperation(file);
41263
+ requests.push({ operation, ...fact });
41264
+ }
41265
+ return requests;
41266
+ }
41267
+ async function fileEndpoint(context, source, files, input) {
41268
+ const route = input.nuxt ? serverRoute(files.project) : undefined;
41269
+ if (route === undefined)
41270
+ return;
41271
+ const handler = await routeHandler(context, source);
41272
+ const operation = (handler === undefined ? undefined : input.operationId(handler)) ?? input.moduleOperation(files.owned);
41273
+ return { operation, ...route };
41274
+ }
41275
+ async function vueHttpFacts(input) {
41276
+ const context = urlContext(ts3, classicChecker(ts3, input.project.checker), input.project.files);
41277
+ const httpRequests = [];
41278
+ const httpEndpoints = [];
41279
+ for (const source of input.project.files) {
41280
+ const owned = relative(input.project.root, source.fileName);
41281
+ httpRequests.push(...await fileRequests(context, source, owned, input));
41282
+ const endpoint = await fileEndpoint(context, source, { project: relative(input.projectRoot, source.fileName), owned }, input);
41283
+ if (endpoint !== undefined)
41284
+ httpEndpoints.push(endpoint);
41285
+ }
41286
+ return { httpRequests, httpEndpoints };
41287
+ }
41288
+
41289
+ // plugins/scanners/vue/src/operations.ts
41290
+ import ts4 from "./typescript.cjs";
41291
+
41292
+ // plugins/scanners/typescript-operations.ts
41293
+ function is(node, kind) {
41294
+ return node !== undefined && node.kind === kind;
41295
+ }
41296
+ function vocabularyOf(k) {
41297
+ return {
41298
+ leading: new Map([
41299
+ [k.TrueKeyword, "true"],
41300
+ [k.FalseKeyword, "false"],
41301
+ [k.NullKeyword, "null"],
41302
+ [k.ThisKeyword, "this"],
41303
+ [k.SuperKeyword, "super"],
41304
+ [k.QuestionDotToken, "?."],
41305
+ [k.SpreadElement, "..."],
41306
+ [k.SpreadAssignment, "..."],
41307
+ [k.ElementAccessExpression, "index"],
41308
+ [k.ReturnStatement, "return"],
41309
+ [k.ForStatement, "for"],
41310
+ [k.ForInStatement, "for"],
41311
+ [k.ForOfStatement, "for"],
41312
+ [k.WhileStatement, "while"],
41313
+ [k.DoStatement, "do"],
41314
+ [k.SwitchStatement, "switch"],
41315
+ [k.CaseClause, "case"],
41316
+ [k.DefaultClause, "default"],
41317
+ [k.BreakStatement, "break"],
41318
+ [k.ContinueStatement, "continue"],
41319
+ [k.ThrowStatement, "throw"],
41320
+ [k.CatchClause, "catch"],
41321
+ [k.AwaitKeyword, "await"],
41322
+ [k.AwaitExpression, "await"],
41323
+ [k.YieldExpression, "yield"],
41324
+ [k.TypeOfExpression, "typeof"],
41325
+ [k.VoidExpression, "void"],
41326
+ [k.DeleteExpression, "delete"]
41327
+ ]),
41328
+ quoted: new Set([k.StringLiteral, k.NoSubstitutionTemplateLiteral, k.TemplateHead, k.TemplateMiddle, k.TemplateTail]),
41329
+ verbatim: new Set([k.NumericLiteral, k.BigIntLiteral, k.RegularExpressionLiteral]),
41330
+ unary: new Map([
41331
+ [k.ExclamationToken, "!"],
41332
+ [k.MinusToken, "-"],
41333
+ [k.PlusToken, "+"],
41334
+ [k.TildeToken, "~"],
41335
+ [k.PlusPlusToken, "++"],
41336
+ [k.MinusMinusToken, "--"]
41337
+ ]),
41338
+ operators: new Set([
41339
+ k.BinaryExpression,
41340
+ k.ConditionalExpression,
41341
+ k.PrefixUnaryExpression,
41342
+ k.PostfixUnaryExpression,
41343
+ k.TypeOfExpression,
41344
+ k.VoidExpression,
41345
+ k.DeleteExpression,
41346
+ k.AwaitExpression,
41347
+ k.YieldExpression
41348
+ ]),
41349
+ types: new Set([
41350
+ k.TypeParameter,
41351
+ k.ArrayType,
41352
+ k.FunctionType,
41353
+ k.LiteralType,
41354
+ k.TypeLiteral,
41355
+ k.ExpressionWithTypeArguments,
41356
+ k.TypeReference,
41357
+ k.ConditionalType
41358
+ ]),
41359
+ assertions: new Set([k.AsExpression, k.SatisfiesExpression, k.NonNullExpression]),
41360
+ functions: new Set([
41361
+ k.ArrowFunction,
41362
+ k.FunctionExpression,
41363
+ k.FunctionDeclaration,
41364
+ k.MethodDeclaration,
41365
+ k.Constructor,
41366
+ k.GetAccessor,
41367
+ k.SetAccessor
41368
+ ])
41369
+ };
41370
+ }
41371
+ function typeScriptOperations(ts) {
41372
+ const k = ts.SyntaxKind;
41373
+ const words = vocabularyOf(k);
41374
+ function executable(node) {
41375
+ if (node.kind === k.ArrowFunction || node.kind === k.FunctionExpression)
41376
+ return true;
41377
+ const declared = node.kind === k.FunctionDeclaration || node.kind === k.MethodDeclaration || node.kind === k.Constructor;
41378
+ return declared && node.body !== undefined;
41379
+ }
41380
+ function callArgumentProperty(node) {
41381
+ const property = is(node.parent, k.PropertyAssignment) ? node.parent : node;
41382
+ if (property.kind !== k.PropertyAssignment && property.kind !== k.MethodDeclaration)
41383
+ return false;
41384
+ if (!is(property.parent, k.ObjectLiteralExpression))
41385
+ return false;
41386
+ let holder = property.parent.parent;
41387
+ while (holder !== undefined && (holder.kind === k.ParenthesizedExpression || words.assertions.has(holder.kind))) {
41388
+ holder = holder.parent;
41389
+ }
41390
+ return is(holder, k.CallExpression) || is(holder, k.NewExpression);
41391
+ }
41392
+ function comparableName(node) {
41393
+ if (!executable(node) || callArgumentProperty(node))
41394
+ return;
41395
+ if (node.kind === k.Constructor)
41396
+ return "constructor";
41397
+ const name = node.name;
41398
+ if (is(name, k.Identifier))
41399
+ return name.text;
41400
+ const parent = node.parent;
41401
+ if (is(parent, k.PropertyAssignment) || is(parent, k.VariableDeclaration))
41402
+ return parent.name.getText();
41403
+ return;
41404
+ }
41405
+ function operationFields(node) {
41406
+ const name = comparableName(node);
41407
+ if (name === undefined)
41408
+ return { name: "(anonymous)" };
41409
+ const source = node.getSourceFile();
41410
+ const line = (position) => source.getLineAndCharacterOfPosition(position).line + 1;
41411
+ return { name, startLine: line(node.getStart()), endLine: line(node.end), tokens: tokenize(k, words, node) };
41412
+ }
41413
+ return { executable, operationFields };
41414
+ }
41415
+ function lookup(scope, name) {
41416
+ return scope.slots.get(name) ?? (scope.parent === undefined ? undefined : lookup(scope.parent, name));
41417
+ }
41418
+ function tokenize(k, words, operation) {
41419
+ const tokens = [];
41420
+ let next = 0;
41421
+ let scope = { slots: new Map };
41422
+ function bindPattern(part) {
41423
+ if (part === undefined)
41424
+ return;
41425
+ if (is(part, k.Identifier)) {
41426
+ scope.slots.set(part.text, next++);
41427
+ return;
41428
+ }
41429
+ if (is(part, k.BindingElement))
41430
+ bindElement(part);
41431
+ else
41432
+ part.forEachChild((child) => bindPattern(child));
41433
+ }
41434
+ function bindElement(element) {
41435
+ const key = element.propertyName ?? (is(element.parent, k.ObjectBindingPattern) ? element.name : undefined);
41436
+ if (is(key, k.Identifier) || is(key, k.StringLiteral) || is(key, k.NumericLiteral))
41437
+ tokens.push(`k.${key.text}`);
41438
+ else
41439
+ walk(key);
41440
+ bindPattern(element.name);
41441
+ walk(element.initializer);
41442
+ }
41443
+ function inScope(run) {
41444
+ const previous = scope;
41445
+ scope = { parent: previous, slots: new Map };
41446
+ run();
41447
+ scope = previous;
41448
+ }
41449
+ function declare(node) {
41450
+ bindPattern(node.name);
41451
+ walk(node.initializer);
41452
+ }
41453
+ function declareParameters(node) {
41454
+ for (const parameter of node.parameters)
41455
+ if (is(parameter, k.Parameter))
41456
+ declare(parameter);
41457
+ }
41458
+ function identifierToken(node) {
41459
+ const parent = node.parent;
41460
+ if (is(parent, k.PropertyAccessExpression) && parent.name === node)
41461
+ return `.${node.text}`;
41462
+ const key = is(parent, k.PropertyAssignment) || is(parent, k.ShorthandPropertyAssignment);
41463
+ if (key && parent.name === node)
41464
+ return `k.${node.text}`;
41465
+ const slot = lookup(scope, node.text);
41466
+ return slot === undefined ? node.text : `$${slot}`;
41467
+ }
41468
+ function groupsOperator(expression) {
41469
+ let node = expression;
41470
+ while (words.assertions.has(node.kind))
41471
+ node = node.expression;
41472
+ return words.operators.has(node.kind);
41473
+ }
41474
+ function emitText(node) {
41475
+ if (words.quoted.has(node.kind))
41476
+ tokens.push(JSON.stringify(node.text));
41477
+ else if (words.verbatim.has(node.kind))
41478
+ tokens.push(node.text);
41479
+ else
41480
+ return false;
41481
+ return true;
41482
+ }
41483
+ function emitCall(node) {
41484
+ if (!is(node, k.CallExpression) && !is(node, k.NewExpression))
41485
+ return false;
41486
+ if (node.kind === k.NewExpression)
41487
+ tokens.push("new");
41488
+ walk(node.expression);
41489
+ walk(node.questionDotToken);
41490
+ tokens.push("call");
41491
+ for (const argument of node.arguments ?? [])
41492
+ walk(argument);
41493
+ return true;
41494
+ }
41495
+ function emitOperator(node) {
41496
+ if (is(node, k.BinaryExpression)) {
41497
+ walk(node.left);
41498
+ tokens.push(node.operatorToken.getText());
41499
+ walk(node.right);
41500
+ } else if (is(node, k.PrefixUnaryExpression)) {
41501
+ tokens.push(words.unary.get(node.operator));
41502
+ walk(node.operand);
41503
+ } else if (is(node, k.PostfixUnaryExpression)) {
41504
+ walk(node.operand);
41505
+ tokens.push(words.unary.get(node.operator));
41506
+ } else if (is(node, k.ConditionalExpression)) {
41507
+ walk(node.condition);
41508
+ tokens.push("?");
41509
+ walk(node.whenTrue);
41510
+ tokens.push(":");
41511
+ walk(node.whenFalse);
41512
+ } else if (is(node, k.ParenthesizedExpression) && groupsOperator(node.expression)) {
41513
+ tokens.push("(");
41514
+ walk(node.expression);
41515
+ tokens.push(")");
41516
+ } else
41517
+ return false;
41518
+ return true;
41519
+ }
41520
+ function emitBranches(node) {
41521
+ if (is(node, k.IfStatement)) {
41522
+ tokens.push("if");
41523
+ walk(node.expression);
41524
+ walk(node.thenStatement);
41525
+ branch("else", node.elseStatement);
41526
+ } else if (is(node, k.TryStatement)) {
41527
+ tokens.push("try");
41528
+ walk(node.tryBlock);
41529
+ walk(node.catchClause);
41530
+ branch("finally", node.finallyBlock);
41531
+ } else
41532
+ return false;
41533
+ return true;
41534
+ }
41535
+ function branch(keyword, statement) {
41536
+ if (statement === undefined)
41537
+ return;
41538
+ tokens.push(keyword);
41539
+ walk(statement);
41540
+ }
41541
+ function emitStructure(node) {
41542
+ if (node.kind === k.Block)
41543
+ inScope(() => node.forEachChild((child) => walk(child)));
41544
+ else if (is(node, k.VariableDeclaration) || is(node, k.Parameter))
41545
+ declare(node);
41546
+ else if (words.functions.has(node.kind)) {
41547
+ tokens.push("fn");
41548
+ inScope(() => {
41549
+ declareParameters(node);
41550
+ walk(node.body);
41551
+ });
41552
+ } else
41553
+ return false;
41554
+ return true;
41555
+ }
41556
+ function walk(node) {
41557
+ if (node === undefined || words.types.has(node.kind) || emitStructure(node))
41558
+ return;
41559
+ if (is(node, k.Identifier) || is(node, k.PrivateIdentifier)) {
41560
+ tokens.push(identifierToken(node));
41561
+ return;
41562
+ }
41563
+ if (emitText(node) || emitCall(node) || emitOperator(node) || emitBranches(node))
41564
+ return;
41565
+ const leading = words.leading.get(node.kind);
41566
+ if (leading !== undefined)
41567
+ tokens.push(leading);
41568
+ node.forEachChild((child) => walk(child));
41569
+ }
41570
+ declareParameters(operation);
41571
+ walk(operation.body);
41572
+ return tokens;
41573
+ }
41574
+
41575
+ // plugins/scanners/vue/src/sfc.ts
41576
+ var import_language_core3 = __toESM(require_language_core2(), 1);
41577
+ function sfcScripts(file, text) {
41578
+ const { descriptor } = import_language_core3.parse(text);
41579
+ const blocks = [
41580
+ ...descriptor.script ? [{ block: descriptor.script, setup: false }] : [],
41581
+ ...descriptor.scriptSetup ? [{ block: descriptor.scriptSetup, setup: true }] : []
41582
+ ].sort((left, right) => left.block.loc.start.offset - right.block.loc.start.offset);
41583
+ return blocks.map(({ block, setup }) => ({
41584
+ fileName: `${file}.${block.lang ?? "js"}`,
41585
+ text: text.slice(0, block.loc.start.offset).replace(/[^\n]/g, " ") + block.content,
41586
+ setup
41587
+ }));
41588
+ }
41589
+
41590
+ // plugins/scanners/vue/src/operations.ts
41591
+ var { operationFields } = typeScriptOperations(ts4);
41592
+ function comparedOperations(file, text) {
41593
+ const operations = [];
41594
+ for (const script of sfcScripts(file, text)) {
41595
+ const source = ts4.createSourceFile(script.fileName, script.text, ts4.ScriptTarget.Latest, true);
41596
+ const visit = (node) => {
41597
+ const fields = operationFields(node);
41598
+ if (fields.tokens !== undefined) {
41599
+ const position = node.getStart(source);
41600
+ operations.push({ id: `${file}#${position}`, file, position, ...fields });
41601
+ }
41602
+ node.forEachChild(visit);
41603
+ };
41604
+ source.forEachChild(visit);
41605
+ }
41606
+ return operations;
41607
+ }
41608
+ function addComparedOperations(project, operations) {
41609
+ for (const source of project.files) {
41610
+ if (!project.sfc(source.fileName))
41611
+ continue;
41612
+ const file = relative(project.root, source.fileName);
41613
+ for (const operation of comparedOperations(file, project.text(source.fileName))) {
41614
+ operations.set(operation.id, operation);
41615
+ }
41616
+ }
41617
+ }
41618
+
41619
+ // plugins/scanners/vue/src/outline.ts
41620
+ import { readFile as readFile2 } from "fs/promises";
41621
+ import path4 from "path";
41622
+ import ts5 from "./typescript.cjs";
41623
+
41624
+ // plugins/scanners/typescript-outline.ts
41625
+ function is2(node, kind) {
41626
+ return node !== undefined && node.kind === kind;
41627
+ }
41628
+ var NO_EXPORTS = new Set;
41629
+ function hasModifier(node, kind) {
41630
+ return node.modifiers?.some((modifier) => modifier.kind === kind) ?? false;
41631
+ }
41632
+ function listedExports(k, statement) {
41633
+ if (is2(statement, k.ExportAssignment)) {
41634
+ return is2(statement.expression, k.Identifier) ? [statement.expression.text] : [];
41635
+ }
41636
+ if (!is2(statement, k.ExportDeclaration) || statement.moduleSpecifier !== undefined)
41637
+ return [];
41638
+ const clause = statement.exportClause;
41639
+ if (!is2(clause, k.NamedExports))
41640
+ return [];
41641
+ return clause.elements.map((element) => (element.propertyName ?? element.name).text);
41642
+ }
41643
+ function topLevelVisibility(scope, name, statement) {
41644
+ if (scope.topLevelPrivate)
41645
+ return "private";
41646
+ return hasModifier(statement, scope.k.ExportKeyword) || scope.exported.has(name) ? "public" : "private";
41647
+ }
41648
+ function memberVisibility(scope, member) {
41649
+ const { k } = scope;
41650
+ if (is2(member.name, k.PrivateIdentifier) || hasModifier(member, k.PrivateKeyword))
41651
+ return "private";
41652
+ return hasModifier(member, k.ProtectedKeyword) ? "protected" : "public";
41653
+ }
41654
+ function symbolAt(scope, name, node, visibility) {
41655
+ const line = scope.source.getLineAndCharacterOfPosition(node.getStart()).line + 1;
41656
+ return { name, line, visibility, entry: scope.symbols.includes(name) };
41657
+ }
41658
+ function functionDeclarations(scope, statement) {
41659
+ const { k } = scope;
41660
+ if (is2(statement, k.FunctionDeclaration) && is2(statement.name, k.Identifier)) {
41661
+ const name = statement.name.text;
41662
+ return [{ kind: "function", ...symbolAt(scope, name, statement.name, topLevelVisibility(scope, name, statement)) }];
41663
+ }
41664
+ if (!is2(statement, k.VariableStatement))
41665
+ return [];
41666
+ return statement.declarationList.declarations.flatMap((declaration) => {
41667
+ const { name, initializer } = declaration;
41668
+ if (!is2(name, k.Identifier))
41669
+ return [];
41670
+ if (!is2(initializer, k.ArrowFunction) && !is2(initializer, k.FunctionExpression))
41671
+ return [];
41672
+ return [{ kind: "function", ...symbolAt(scope, name.text, name, topLevelVisibility(scope, name.text, statement)) }];
40138
41673
  });
40139
41674
  }
41675
+ function memberName(scope, name) {
41676
+ const { k } = scope;
41677
+ if (is2(name, k.Identifier) || is2(name, k.PrivateIdentifier))
41678
+ return name;
41679
+ return is2(name, k.StringLiteral) || is2(name, k.NumericLiteral) ? name : undefined;
41680
+ }
41681
+ function typeMember(scope, member) {
41682
+ const { k } = scope;
41683
+ if (member.kind === k.Constructor)
41684
+ return [symbolAt(scope, "constructor", member, memberVisibility(scope, member))];
41685
+ if (!is2(member, k.MethodDeclaration) && !is2(member, k.MethodSignature))
41686
+ return [];
41687
+ const name = memberName(scope, member.name);
41688
+ return name === undefined ? [] : [symbolAt(scope, name.text, name, memberVisibility(scope, member))];
41689
+ }
41690
+ function typeMembers(k, statement) {
41691
+ if (is2(statement, k.ClassDeclaration) || is2(statement, k.InterfaceDeclaration))
41692
+ return statement.members;
41693
+ return statement.kind === k.EnumDeclaration ? [] : undefined;
41694
+ }
41695
+ function typeDeclaration(scope, statement) {
41696
+ const members = typeMembers(scope.k, statement);
41697
+ const { name } = statement;
41698
+ if (members === undefined || !is2(name, scope.k.Identifier))
41699
+ return [];
41700
+ return [{
41701
+ kind: "type",
41702
+ ...symbolAt(scope, name.text, name, topLevelVisibility(scope, name.text, statement)),
41703
+ members: members.flatMap((member) => typeMember(scope, member))
41704
+ }];
41705
+ }
41706
+ function moduleStatements(k, statement) {
41707
+ let body = statement.body;
41708
+ while (is2(body, k.ModuleDeclaration))
41709
+ body = body.body;
41710
+ return is2(body, k.ModuleBlock) ? body.statements : [];
41711
+ }
41712
+ function declarationsIn(scope, statements) {
41713
+ return statements.flatMap((statement) => {
41714
+ if (is2(statement, scope.k.ModuleDeclaration)) {
41715
+ return declarationsIn({ ...scope, exported: NO_EXPORTS }, moduleStatements(scope.k, statement));
41716
+ }
41717
+ return [...functionDeclarations(scope, statement), ...typeDeclaration(scope, statement)];
41718
+ });
41719
+ }
41720
+ function outlineSource(ts, source, context) {
41721
+ const k = ts.SyntaxKind;
41722
+ const exported = new Set([...source.statements.flatMap((statement) => listedExports(k, statement)), ...context.exported ?? []]);
41723
+ const scope = { k, source, symbols: context.symbols, exported, topLevelPrivate: context.topLevelPrivate ?? false };
41724
+ return declarationsIn(scope, source.statements);
41725
+ }
41726
+ function outlineDeclarations(ts, block) {
41727
+ return outlineSource(ts, ts.createSourceFile(block.fileName, block.text, ts.ScriptTarget.Latest, true), block);
41728
+ }
41729
+
41730
+ // plugins/scanners/vue/src/outline.ts
41731
+ var SCRIPT = /\.(?:vue|ts|js)$/;
41732
+ function componentDeclarations(fileName, text, symbols) {
41733
+ return sfcScripts(fileName, text).flatMap((script) => outlineDeclarations(ts5, {
41734
+ fileName: script.fileName,
41735
+ text: script.text,
41736
+ symbols,
41737
+ topLevelPrivate: script.setup
41738
+ }));
41739
+ }
41740
+ async function readVueOutline(repositoryRoot, references) {
41741
+ const files = [];
41742
+ for (const reference of references.filter((reference) => SCRIPT.test(reference.file))) {
41743
+ const fileName = path4.join(repositoryRoot, reference.file);
41744
+ const text = await readFile2(fileName, "utf8");
41745
+ const declarations = reference.file.endsWith(".vue") ? componentDeclarations(fileName, text, reference.symbols) : outlineDeclarations(ts5, { fileName, text, symbols: reference.symbols });
41746
+ if (declarations.length > 0)
41747
+ files.push({ file: reference.file, declarations });
41748
+ }
41749
+ return files;
41750
+ }
40140
41751
 
40141
41752
  // plugins/scanners/vue/src/index.ts
40142
41753
  async function scanVue(root) {
40143
41754
  const parts = [];
40144
- for (const project of await frameworkProjects(root, "vue")) {
41755
+ for (const project of await frameworkProjects(root, "vue", [".vue"])) {
40145
41756
  const observation = await scanVueProject(project, root);
40146
41757
  if (observation)
40147
41758
  parts.push({ key: relative(root, project), observation });
@@ -40159,23 +41770,48 @@ async function scanVueProject(projectRoot, root) {
40159
41770
  if (sfc)
40160
41771
  evidence.inspect(source.fileName, sfc);
40161
41772
  }
41773
+ const { httpRequests, httpEndpoints } = await vueHttpFacts({
41774
+ project,
41775
+ projectRoot,
41776
+ nuxt: hasDependency(manifest, "nuxt"),
41777
+ operationId: (node) => evidence.operationId(node),
41778
+ moduleOperation: (file) => {
41779
+ const id = `${file}#module`;
41780
+ evidence.operations.set(id, { id, file, name: "(module)" });
41781
+ return id;
41782
+ }
41783
+ });
41784
+ addComparedOperations(project, evidence.operations);
40162
41785
  const files = project.files.map((source) => ({ file: relative(root, source.fileName), symbols: [] }));
41786
+ const sourceUnits = project.files.flatMap((source) => project.sourceUnit(source.fileName) ?? []);
41787
+ for (const file of new Set(sourceUnits.flatMap((unit) => unit.files))) {
41788
+ if (!files.some((source) => source.file === file))
41789
+ files.push({ file, symbols: [] });
41790
+ }
40163
41791
  return createScanObservation({
40164
41792
  scanner: { id: "vue", technology: "typescript/vue", engine: "@vue/language-core", engineVersion: "3.3.11" },
40165
- roots: [{ id: "vue-project", kind: "package", name: manifest.name, file: relative(root, path3.join(projectRoot, "package.json")) }],
41793
+ roots: [{ id: "vue-project", kind: "package", name: manifest.name, file: relative(root, path5.join(projectRoot, "package.json")) }],
40166
41794
  files: files.map((file) => ({ ...file, roots: ["vue-project"] })),
41795
+ sourceUnits,
40167
41796
  operations: [...evidence.operations.values()],
40168
41797
  invocations: evidence.invocations,
40169
- diagnostics: evidence.diagnostics
41798
+ diagnostics: evidence.diagnostics,
41799
+ ...httpEndpoints.length ? { httpEndpoints } : {},
41800
+ ...httpRequests.length ? { httpRequests } : {}
40170
41801
  });
40171
41802
  }
40172
41803
  var src_default = {
40173
41804
  id: "vue",
40174
- watch: { include: ["**/*.vue", "**/*.ts", "**/tsconfig*.json", "**/package.json"], exclude: [] },
41805
+ watch: { include: ["**/*.vue", "**/*.ts", "**/*.js", "**/*.html", "**/*.css", "**/*.scss", "**/*.sass", "**/*.less", "**/*.styl", "**/tsconfig*.json", "**/package.json"], exclude: [] },
41806
+ readCodeStructure: readVueOutline,
41807
+ listSourceFiles: (root) => frameworkSourceFiles({
41808
+ root,
41809
+ dependency: "vue",
41810
+ projects: [".vue"],
41811
+ sources: [".vue", ".ts", ".tsx", ".js", ".jsx", ".html", ".css", ".scss", ".sass", ".less", ".styl"]
41812
+ }),
40175
41813
  checkReadiness: async (root) => {
40176
- const projects = await frameworkProjects(root, "vue");
40177
- if (!projects.length)
40178
- throw new Error("VUE_PROJECT_REQUIRED: No Vue project declaration was found.");
41814
+ const projects = await frameworkProjects(root, "vue", [".vue"]);
40179
41815
  for (const project of projects)
40180
41816
  vueProject(project, root);
40181
41817
  },