@groma/scanner-vue 0.1.3 → 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.
- package/README.md +149 -6
- package/dist/index.js +1658 -33
- 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,9 +39921,12 @@ var excluded = new Set([
|
|
|
39803
39921
|
"groma",
|
|
39804
39922
|
".groma"
|
|
39805
39923
|
]);
|
|
39806
|
-
async function
|
|
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) &&
|
|
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");
|
|
@@ -39893,10 +40014,32 @@ class VueProject {
|
|
|
39893
40014
|
const code = this.language.scripts.get(file)?.generated?.root;
|
|
39894
40015
|
return code instanceof import_language_core.VueVirtualCode ? code : undefined;
|
|
39895
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
|
+
}
|
|
39896
40036
|
validateSfc(file) {
|
|
39897
40037
|
const sfc = this.sfc(file);
|
|
39898
40038
|
if (!sfc)
|
|
39899
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
|
+
}
|
|
39900
40043
|
const errors = [...sfc.vueSfc?.errors ?? [], ...sfc.ir.template?.errors ?? []];
|
|
39901
40044
|
if (errors.length)
|
|
39902
40045
|
throw new Error(`${file}: ${errors.map((error) => typeof error === "string" ? error : error.message).join(`
|
|
@@ -39939,20 +40082,19 @@ function vueProject(root, repositoryRoot = root) {
|
|
|
39939
40082
|
if (!hasDependency(manifest, "vue"))
|
|
39940
40083
|
return;
|
|
39941
40084
|
try {
|
|
39942
|
-
createRequire(manifestFile).resolve("vue/package.json");
|
|
39943
40085
|
return { manifest, project: new VueProject(root, repositoryRoot) };
|
|
39944
40086
|
} catch (error) {
|
|
39945
|
-
throw new Error(`
|
|
40087
|
+
throw new Error(`VUE_SOURCE_INVALID: Check the project tsconfig.json and Vue syntax. ${error}`);
|
|
39946
40088
|
}
|
|
39947
40089
|
}
|
|
39948
40090
|
|
|
39949
40091
|
// plugins/scanners/vue/src/evidence.ts
|
|
39950
|
-
function
|
|
39951
|
-
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);
|
|
39952
40094
|
}
|
|
39953
40095
|
function enclosingOperation(node) {
|
|
39954
40096
|
for (let current = node.parent;current; current = current.parent) {
|
|
39955
|
-
if (
|
|
40097
|
+
if (operation2(current))
|
|
39956
40098
|
return current;
|
|
39957
40099
|
}
|
|
39958
40100
|
return;
|
|
@@ -39964,9 +40106,11 @@ function immutable(node) {
|
|
|
39964
40106
|
return ts2.isVariableDeclaration(parent) && ts2.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts2.NodeFlags.Const) !== 0;
|
|
39965
40107
|
}
|
|
39966
40108
|
function includesEvent(type, event) {
|
|
39967
|
-
if (type
|
|
39968
|
-
return
|
|
39969
|
-
|
|
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));
|
|
39970
40114
|
}
|
|
39971
40115
|
|
|
39972
40116
|
class VueEvidence {
|
|
@@ -40023,7 +40167,7 @@ class VueEvidence {
|
|
|
40023
40167
|
continue;
|
|
40024
40168
|
for (const signature of this.project.checker.getTypeAtLocation(node).getCallSignatures()) {
|
|
40025
40169
|
const declaration = signature.declaration;
|
|
40026
|
-
if (declaration &&
|
|
40170
|
+
if (declaration && operation2(declaration) && immutable(declaration) && this.project.owned(declaration.getSourceFile()))
|
|
40027
40171
|
candidates.add(declaration);
|
|
40028
40172
|
}
|
|
40029
40173
|
}
|
|
@@ -40053,15 +40197,14 @@ class VueEvidence {
|
|
|
40053
40197
|
emissions(sfc, event) {
|
|
40054
40198
|
const symbol = this.emitSymbol(sfc);
|
|
40055
40199
|
const source = this.project.program.getSourceFile(sfc.fileName);
|
|
40056
|
-
if (!symbol || !source)
|
|
40200
|
+
if (!symbol || !source || !this.declaresEvent(sfc, event))
|
|
40057
40201
|
return [];
|
|
40058
40202
|
const calls = [];
|
|
40059
40203
|
const checker = this.project.checker;
|
|
40060
40204
|
function visit(node) {
|
|
40061
40205
|
if (ts2.isCallExpression(node) && checker.getSymbolAtLocation(node.expression) === symbol) {
|
|
40062
40206
|
const argument = node.arguments[0];
|
|
40063
|
-
|
|
40064
|
-
if (argument && ts2.isStringLiteral(argument) && argument.text === event && parameter && includesEvent(checker.getTypeOfSymbolAtLocation(parameter, node), event))
|
|
40207
|
+
if (argument && ts2.isStringLiteral(argument) && argument.text === event)
|
|
40065
40208
|
calls.push(node);
|
|
40066
40209
|
}
|
|
40067
40210
|
ts2.forEachChild(node, visit);
|
|
@@ -40069,13 +40212,33 @@ class VueEvidence {
|
|
|
40069
40212
|
visit(source);
|
|
40070
40213
|
return calls;
|
|
40071
40214
|
}
|
|
40072
|
-
|
|
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) {
|
|
40073
40233
|
const position = this.project.position(node);
|
|
40074
40234
|
if (position === undefined)
|
|
40075
40235
|
return;
|
|
40076
40236
|
const file = relative(this.project.root, node.getSourceFile().fileName);
|
|
40077
40237
|
const id = `${file}#${position}`;
|
|
40078
|
-
|
|
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;
|
|
40079
40242
|
this.operations.set(id, { id, file, position, name: name ?? `callback at ${this.project.line(node.getSourceFile().fileName, position)}` });
|
|
40080
40243
|
return id;
|
|
40081
40244
|
}
|
|
@@ -40097,8 +40260,8 @@ class VueEvidence {
|
|
|
40097
40260
|
const position = this.project.position(call);
|
|
40098
40261
|
if (!caller || position === undefined)
|
|
40099
40262
|
continue;
|
|
40100
|
-
const source = this.
|
|
40101
|
-
const targetId = this.
|
|
40263
|
+
const source = this.operationId(caller);
|
|
40264
|
+
const targetId = this.operationId(target);
|
|
40102
40265
|
if (!source || !targetId)
|
|
40103
40266
|
continue;
|
|
40104
40267
|
this.invocations.push({
|
|
@@ -40117,7 +40280,26 @@ class VueEvidence {
|
|
|
40117
40280
|
}
|
|
40118
40281
|
|
|
40119
40282
|
// plugins/scanners/vue/src/index.ts
|
|
40283
|
+
import path5 from "path";
|
|
40284
|
+
|
|
40285
|
+
// plugins/scanners/typescript-project.ts
|
|
40120
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
|
+
}
|
|
40121
40303
|
|
|
40122
40304
|
// plugins/scanners/observations.ts
|
|
40123
40305
|
function combineObservations(parts) {
|
|
@@ -40128,6 +40310,9 @@ function combineObservations(parts) {
|
|
|
40128
40310
|
const operations = [];
|
|
40129
40311
|
const invocations = [];
|
|
40130
40312
|
const diagnostics = [];
|
|
40313
|
+
const sourceUnits = [];
|
|
40314
|
+
const httpEndpoints = [];
|
|
40315
|
+
const httpRequests = [];
|
|
40131
40316
|
for (const { key, observation } of parts) {
|
|
40132
40317
|
const id = (value) => JSON.stringify([key, value]);
|
|
40133
40318
|
roots.push(...observation.roots.map((root) => ({ ...root, id: id(root.id), ...root.parent ? { parent: id(root.parent) } : {} })));
|
|
@@ -40139,7 +40324,10 @@ function combineObservations(parts) {
|
|
|
40139
40324
|
}
|
|
40140
40325
|
operations.push(...(observation.operations ?? []).map((operation) => ({ ...operation, id: id(operation.id) })));
|
|
40141
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) })));
|
|
40142
40329
|
diagnostics.push(...observation.diagnostics);
|
|
40330
|
+
sourceUnits.push(...observation.sourceUnits ?? []);
|
|
40143
40331
|
}
|
|
40144
40332
|
return createScanObservation({
|
|
40145
40333
|
scanner: parts[0].observation.scanner,
|
|
@@ -40147,10 +40335,1420 @@ function combineObservations(parts) {
|
|
|
40147
40335
|
files: [...files.values()],
|
|
40148
40336
|
operations,
|
|
40149
40337
|
invocations,
|
|
40150
|
-
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 } : {}
|
|
40151
40342
|
});
|
|
40152
40343
|
}
|
|
40153
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)) }];
|
|
41673
|
+
});
|
|
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
|
+
}
|
|
41751
|
+
|
|
40154
41752
|
// plugins/scanners/vue/src/index.ts
|
|
40155
41753
|
async function scanVue(root) {
|
|
40156
41754
|
const parts = [];
|
|
@@ -40172,19 +41770,46 @@ async function scanVueProject(projectRoot, root) {
|
|
|
40172
41770
|
if (sfc)
|
|
40173
41771
|
evidence.inspect(source.fileName, sfc);
|
|
40174
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);
|
|
40175
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
|
+
}
|
|
40176
41791
|
return createScanObservation({
|
|
40177
41792
|
scanner: { id: "vue", technology: "typescript/vue", engine: "@vue/language-core", engineVersion: "3.3.11" },
|
|
40178
|
-
roots: [{ id: "vue-project", kind: "package", name: manifest.name, file: relative(root,
|
|
41793
|
+
roots: [{ id: "vue-project", kind: "package", name: manifest.name, file: relative(root, path5.join(projectRoot, "package.json")) }],
|
|
40179
41794
|
files: files.map((file) => ({ ...file, roots: ["vue-project"] })),
|
|
41795
|
+
sourceUnits,
|
|
40180
41796
|
operations: [...evidence.operations.values()],
|
|
40181
41797
|
invocations: evidence.invocations,
|
|
40182
|
-
diagnostics: evidence.diagnostics
|
|
41798
|
+
diagnostics: evidence.diagnostics,
|
|
41799
|
+
...httpEndpoints.length ? { httpEndpoints } : {},
|
|
41800
|
+
...httpRequests.length ? { httpRequests } : {}
|
|
40183
41801
|
});
|
|
40184
41802
|
}
|
|
40185
41803
|
var src_default = {
|
|
40186
41804
|
id: "vue",
|
|
40187
|
-
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
|
+
}),
|
|
40188
41813
|
checkReadiness: async (root) => {
|
|
40189
41814
|
const projects = await frameworkProjects(root, "vue", [".vue"]);
|
|
40190
41815
|
for (const project of projects)
|