docbridge 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -1
- package/README.md +16 -9
- package/dist/bin/darwin-arm64/SHA256SUMS +3 -3
- package/dist/bin/darwin-arm64/docbridge-go-scanner +0 -0
- package/dist/bin/darwin-arm64/docbridge-swift-scanner +0 -0
- package/dist/bin/darwin-arm64/docbridge_dart_scanner +0 -0
- package/dist/bin/linux-x64/SHA256SUMS +2 -2
- package/dist/bin/linux-x64/docbridge-go-scanner +0 -0
- package/dist/bin/linux-x64/docbridge-swift-scanner +1 -1
- package/dist/index.js +1771 -678
- package/dist/workers/java/docbridge-java-scanner.jar +0 -0
- package/dist/workers/python/docbridge_python_scanner/__init__.py +6 -0
- package/dist/workers/python/docbridge_python_scanner/comments.py +177 -0
- package/dist/workers/python/docbridge_python_scanner/declarations.py +318 -0
- package/dist/workers/python/docbridge_python_scanner/links.py +62 -0
- package/dist/workers/python/docbridge_python_scanner/positions.py +75 -0
- package/dist/workers/python/docbridge_python_scanner/protocol.py +105 -0
- package/dist/workers/python/docbridge_python_scanner/scanner.py +214 -0
- package/dist/workers/python/docbridge_python_scanner.py +16 -0
- package/dist/workers/ruby/bin/docbridge-ruby-scanner +10 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/cli.rb +30 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/comments.rb +55 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/compat.rb +39 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/declarations.rb +230 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/links.rb +38 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/positions.rb +50 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/probe.rb +28 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner/protocol.rb +161 -0
- package/dist/workers/ruby/lib/docbridge_ruby_scanner.rb +21 -0
- package/docs/README.md +10 -3
- package/docs/user/automation.md +8 -4
- package/docs/user/commands.md +11 -3
- package/docs/user/configuration.md +111 -17
- package/docs/user/getting-started.md +1 -1
- package/docs/user/linking.md +106 -10
- package/docs/user/troubleshooting.md +26 -9
- package/package.json +1 -1
- package/schemas/common-output.schema.json +11 -1
- package/schemas/context-output.schema.json +11 -1
- package/schemas/docbridge.schema.json +118 -2
- package/schemas/graph-output.schema.json +11 -1
- package/schemas/scanner-worker.schema.json +1 -1
- package/templates/skills/docbridge/SKILL.md +12 -2
package/dist/index.js
CHANGED
|
@@ -169132,11 +169132,11 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169132
169132
|
const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
|
|
169133
169133
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
169134
169134
|
}
|
|
169135
|
-
optimizeNames(names,
|
|
169135
|
+
optimizeNames(names, constants) {
|
|
169136
169136
|
if (!names[this.name.str])
|
|
169137
169137
|
return;
|
|
169138
169138
|
if (this.rhs)
|
|
169139
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
169139
|
+
this.rhs = optimizeExpr(this.rhs, names, constants);
|
|
169140
169140
|
return this;
|
|
169141
169141
|
}
|
|
169142
169142
|
get names() {
|
|
@@ -169154,10 +169154,10 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169154
169154
|
render({ _n }) {
|
|
169155
169155
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
169156
169156
|
}
|
|
169157
|
-
optimizeNames(names,
|
|
169157
|
+
optimizeNames(names, constants) {
|
|
169158
169158
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
|
|
169159
169159
|
return;
|
|
169160
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
169160
|
+
this.rhs = optimizeExpr(this.rhs, names, constants);
|
|
169161
169161
|
return this;
|
|
169162
169162
|
}
|
|
169163
169163
|
get names() {
|
|
@@ -169223,8 +169223,8 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169223
169223
|
optimizeNodes() {
|
|
169224
169224
|
return `${this.code}` ? this : undefined;
|
|
169225
169225
|
}
|
|
169226
|
-
optimizeNames(names,
|
|
169227
|
-
this.code = optimizeExpr(this.code, names,
|
|
169226
|
+
optimizeNames(names, constants) {
|
|
169227
|
+
this.code = optimizeExpr(this.code, names, constants);
|
|
169228
169228
|
return this;
|
|
169229
169229
|
}
|
|
169230
169230
|
get names() {
|
|
@@ -169254,12 +169254,12 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169254
169254
|
}
|
|
169255
169255
|
return nodes.length > 0 ? this : undefined;
|
|
169256
169256
|
}
|
|
169257
|
-
optimizeNames(names,
|
|
169257
|
+
optimizeNames(names, constants) {
|
|
169258
169258
|
const { nodes } = this;
|
|
169259
169259
|
let i = nodes.length;
|
|
169260
169260
|
while (i--) {
|
|
169261
169261
|
const n = nodes[i];
|
|
169262
|
-
if (n.optimizeNames(names,
|
|
169262
|
+
if (n.optimizeNames(names, constants))
|
|
169263
169263
|
continue;
|
|
169264
169264
|
subtractNames(names, n.names);
|
|
169265
169265
|
nodes.splice(i, 1);
|
|
@@ -169316,12 +169316,12 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169316
169316
|
return;
|
|
169317
169317
|
return this;
|
|
169318
169318
|
}
|
|
169319
|
-
optimizeNames(names,
|
|
169319
|
+
optimizeNames(names, constants) {
|
|
169320
169320
|
var _a;
|
|
169321
|
-
this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names,
|
|
169322
|
-
if (!(super.optimizeNames(names,
|
|
169321
|
+
this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants);
|
|
169322
|
+
if (!(super.optimizeNames(names, constants) || this.else))
|
|
169323
169323
|
return;
|
|
169324
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
169324
|
+
this.condition = optimizeExpr(this.condition, names, constants);
|
|
169325
169325
|
return this;
|
|
169326
169326
|
}
|
|
169327
169327
|
get names() {
|
|
@@ -169346,10 +169346,10 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169346
169346
|
render(opts) {
|
|
169347
169347
|
return `for(${this.iteration})` + super.render(opts);
|
|
169348
169348
|
}
|
|
169349
|
-
optimizeNames(names,
|
|
169350
|
-
if (!super.optimizeNames(names,
|
|
169349
|
+
optimizeNames(names, constants) {
|
|
169350
|
+
if (!super.optimizeNames(names, constants))
|
|
169351
169351
|
return;
|
|
169352
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
169352
|
+
this.iteration = optimizeExpr(this.iteration, names, constants);
|
|
169353
169353
|
return this;
|
|
169354
169354
|
}
|
|
169355
169355
|
get names() {
|
|
@@ -169387,10 +169387,10 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169387
169387
|
render(opts) {
|
|
169388
169388
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
169389
169389
|
}
|
|
169390
|
-
optimizeNames(names,
|
|
169391
|
-
if (!super.optimizeNames(names,
|
|
169390
|
+
optimizeNames(names, constants) {
|
|
169391
|
+
if (!super.optimizeNames(names, constants))
|
|
169392
169392
|
return;
|
|
169393
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
169393
|
+
this.iterable = optimizeExpr(this.iterable, names, constants);
|
|
169394
169394
|
return this;
|
|
169395
169395
|
}
|
|
169396
169396
|
get names() {
|
|
@@ -169435,11 +169435,11 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169435
169435
|
(_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
|
|
169436
169436
|
return this;
|
|
169437
169437
|
}
|
|
169438
|
-
optimizeNames(names,
|
|
169438
|
+
optimizeNames(names, constants) {
|
|
169439
169439
|
var _a, _b;
|
|
169440
|
-
super.optimizeNames(names,
|
|
169441
|
-
(_a = this.catch) === null || _a === undefined || _a.optimizeNames(names,
|
|
169442
|
-
(_b = this.finally) === null || _b === undefined || _b.optimizeNames(names,
|
|
169440
|
+
super.optimizeNames(names, constants);
|
|
169441
|
+
(_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants);
|
|
169442
|
+
(_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants);
|
|
169443
169443
|
return this;
|
|
169444
169444
|
}
|
|
169445
169445
|
get names() {
|
|
@@ -169713,7 +169713,7 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169713
169713
|
function addExprNames(names, from) {
|
|
169714
169714
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
169715
169715
|
}
|
|
169716
|
-
function optimizeExpr(expr, names,
|
|
169716
|
+
function optimizeExpr(expr, names, constants) {
|
|
169717
169717
|
if (expr instanceof code_1.Name)
|
|
169718
169718
|
return replaceName(expr);
|
|
169719
169719
|
if (!canOptimize(expr))
|
|
@@ -169728,14 +169728,14 @@ var require_codegen = __commonJS((exports) => {
|
|
|
169728
169728
|
return items;
|
|
169729
169729
|
}, []));
|
|
169730
169730
|
function replaceName(n) {
|
|
169731
|
-
const c =
|
|
169731
|
+
const c = constants[n.str];
|
|
169732
169732
|
if (c === undefined || names[n.str] !== 1)
|
|
169733
169733
|
return n;
|
|
169734
169734
|
delete names[n.str];
|
|
169735
169735
|
return c;
|
|
169736
169736
|
}
|
|
169737
169737
|
function canOptimize(e) {
|
|
169738
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
169738
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== undefined);
|
|
169739
169739
|
}
|
|
169740
169740
|
}
|
|
169741
169741
|
function subtractNames(names, from) {
|
|
@@ -170197,8 +170197,8 @@ var require_dataType = __commonJS((exports) => {
|
|
|
170197
170197
|
return types;
|
|
170198
170198
|
}
|
|
170199
170199
|
exports.getSchemaTypes = getSchemaTypes;
|
|
170200
|
-
function getJSONTypes(
|
|
170201
|
-
const types = Array.isArray(
|
|
170200
|
+
function getJSONTypes(ts3) {
|
|
170201
|
+
const types = Array.isArray(ts3) ? ts3 : ts3 ? [ts3] : [];
|
|
170202
170202
|
if (types.every(rules_1.isJSONType))
|
|
170203
170203
|
return types;
|
|
170204
170204
|
throw new Error("type must be JSONType or JSONType[]: " + types.join(","));
|
|
@@ -171223,18 +171223,18 @@ var require_validate = __commonJS((exports) => {
|
|
|
171223
171223
|
});
|
|
171224
171224
|
narrowSchemaTypes(it, types);
|
|
171225
171225
|
}
|
|
171226
|
-
function checkMultipleTypes(it,
|
|
171227
|
-
if (
|
|
171226
|
+
function checkMultipleTypes(it, ts3) {
|
|
171227
|
+
if (ts3.length > 1 && !(ts3.length === 2 && ts3.includes("null"))) {
|
|
171228
171228
|
strictTypesError(it, "use allowUnionTypes to allow union type keyword");
|
|
171229
171229
|
}
|
|
171230
171230
|
}
|
|
171231
|
-
function checkKeywordTypes(it,
|
|
171231
|
+
function checkKeywordTypes(it, ts3) {
|
|
171232
171232
|
const rules = it.self.RULES.all;
|
|
171233
171233
|
for (const keyword in rules) {
|
|
171234
171234
|
const rule = rules[keyword];
|
|
171235
171235
|
if (typeof rule == "object" && (0, applicability_1.shouldUseRule)(it.schema, rule)) {
|
|
171236
171236
|
const { type } = rule.definition;
|
|
171237
|
-
if (type.length && !type.some((t) => hasApplicableType(
|
|
171237
|
+
if (type.length && !type.some((t) => hasApplicableType(ts3, t))) {
|
|
171238
171238
|
strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`);
|
|
171239
171239
|
}
|
|
171240
171240
|
}
|
|
@@ -171243,18 +171243,18 @@ var require_validate = __commonJS((exports) => {
|
|
|
171243
171243
|
function hasApplicableType(schTs, kwdT) {
|
|
171244
171244
|
return schTs.includes(kwdT) || kwdT === "number" && schTs.includes("integer");
|
|
171245
171245
|
}
|
|
171246
|
-
function includesType(
|
|
171247
|
-
return
|
|
171246
|
+
function includesType(ts3, t) {
|
|
171247
|
+
return ts3.includes(t) || t === "integer" && ts3.includes("number");
|
|
171248
171248
|
}
|
|
171249
171249
|
function narrowSchemaTypes(it, withTypes) {
|
|
171250
|
-
const
|
|
171250
|
+
const ts3 = [];
|
|
171251
171251
|
for (const t of it.dataTypes) {
|
|
171252
171252
|
if (includesType(withTypes, t))
|
|
171253
|
-
|
|
171253
|
+
ts3.push(t);
|
|
171254
171254
|
else if (withTypes.includes("integer") && t === "number")
|
|
171255
|
-
|
|
171255
|
+
ts3.push("integer");
|
|
171256
171256
|
}
|
|
171257
|
-
it.dataTypes =
|
|
171257
|
+
it.dataTypes = ts3;
|
|
171258
171258
|
}
|
|
171259
171259
|
function strictTypesError(it, msg) {
|
|
171260
171260
|
const schemaPath = it.schemaEnv.baseId + it.errSchemaPath;
|
|
@@ -175576,12 +175576,12 @@ var require_2020 = __commonJS((exports, module) => {
|
|
|
175576
175576
|
});
|
|
175577
175577
|
|
|
175578
175578
|
// src/cli/index.ts
|
|
175579
|
-
import { existsSync as
|
|
175580
|
-
import { join as
|
|
175579
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync6 } from "node:fs";
|
|
175580
|
+
import { join as join15, resolve as resolve5 } from "node:path";
|
|
175581
175581
|
// package.json
|
|
175582
175582
|
var package_default = {
|
|
175583
175583
|
name: "docbridge",
|
|
175584
|
-
version: "0.
|
|
175584
|
+
version: "0.13.0",
|
|
175585
175585
|
description: "A CLI that links Markdown specifications and code declarations. Runs on Node.js and Bun.",
|
|
175586
175586
|
keywords: [
|
|
175587
175587
|
"bun",
|
|
@@ -175716,15 +175716,8 @@ function collectFiles(projectRoot, patterns) {
|
|
|
175716
175716
|
if (!stat.isFile()) {
|
|
175717
175717
|
continue;
|
|
175718
175718
|
}
|
|
175719
|
-
|
|
175720
|
-
if (!matchGlob(pattern, relPath)) {
|
|
175721
|
-
continue;
|
|
175722
|
-
}
|
|
175723
|
-
if (isCodePattern(pattern) && relPath.endsWith(".d.ts")) {
|
|
175724
|
-
continue;
|
|
175725
|
-
}
|
|
175719
|
+
if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
|
|
175726
175720
|
matched.add(relPath);
|
|
175727
|
-
break;
|
|
175728
175721
|
}
|
|
175729
175722
|
}
|
|
175730
175723
|
};
|
|
@@ -175748,9 +175741,6 @@ function readManagedFile(projectRoot, relativePath) {
|
|
|
175748
175741
|
};
|
|
175749
175742
|
}
|
|
175750
175743
|
}
|
|
175751
|
-
function isCodePattern(pattern) {
|
|
175752
|
-
return pattern.endsWith(".ts");
|
|
175753
|
-
}
|
|
175754
175744
|
function globToRegExp(pattern) {
|
|
175755
175745
|
const segments = pattern.split("/");
|
|
175756
175746
|
const parts = [];
|
|
@@ -175788,11 +175778,49 @@ var KNOWN_CODE_LANGUAGES = [
|
|
|
175788
175778
|
"swift",
|
|
175789
175779
|
"dart",
|
|
175790
175780
|
"rust",
|
|
175791
|
-
"go"
|
|
175781
|
+
"go",
|
|
175782
|
+
"javascript",
|
|
175783
|
+
"python",
|
|
175784
|
+
"ruby",
|
|
175785
|
+
"java"
|
|
175792
175786
|
];
|
|
175793
175787
|
function isCodeLanguage(value) {
|
|
175794
175788
|
return KNOWN_CODE_LANGUAGES.includes(value);
|
|
175795
175789
|
}
|
|
175790
|
+
var LANGUAGE_SUFFIXES = {
|
|
175791
|
+
typescript: [".ts", ".tsx", ".mts", ".cts"],
|
|
175792
|
+
swift: [".swift"],
|
|
175793
|
+
dart: [".dart"],
|
|
175794
|
+
rust: [".rs"],
|
|
175795
|
+
go: [".go"],
|
|
175796
|
+
javascript: [".js", ".jsx", ".mjs", ".cjs"],
|
|
175797
|
+
python: [".py"],
|
|
175798
|
+
ruby: [".rb"],
|
|
175799
|
+
java: [".java"]
|
|
175800
|
+
};
|
|
175801
|
+
var EXCLUDED_SUFFIXES = {
|
|
175802
|
+
typescript: [".d.ts", ".d.mts", ".d.cts"],
|
|
175803
|
+
swift: [],
|
|
175804
|
+
dart: [],
|
|
175805
|
+
rust: [],
|
|
175806
|
+
go: [],
|
|
175807
|
+
javascript: [],
|
|
175808
|
+
python: [],
|
|
175809
|
+
ruby: [],
|
|
175810
|
+
java: []
|
|
175811
|
+
};
|
|
175812
|
+
function codeFenceLanguage(language, filePath) {
|
|
175813
|
+
if (language === "typescript") {
|
|
175814
|
+
return filePath.endsWith(".tsx") ? "tsx" : "ts";
|
|
175815
|
+
}
|
|
175816
|
+
if (language === "javascript") {
|
|
175817
|
+
return filePath.endsWith(".jsx") ? "jsx" : "js";
|
|
175818
|
+
}
|
|
175819
|
+
return language;
|
|
175820
|
+
}
|
|
175821
|
+
function hasExcludedSuffix(language, relPath) {
|
|
175822
|
+
return EXCLUDED_SUFFIXES[language].some((suffix) => relPath.endsWith(suffix));
|
|
175823
|
+
}
|
|
175796
175824
|
function collectCodeFiles(projectRoot, codeInclude) {
|
|
175797
175825
|
const collected = [];
|
|
175798
175826
|
const seen = new Set;
|
|
@@ -175801,7 +175829,7 @@ function collectCodeFiles(projectRoot, codeInclude) {
|
|
|
175801
175829
|
if (entry === undefined) {
|
|
175802
175830
|
continue;
|
|
175803
175831
|
}
|
|
175804
|
-
for (const relPath of
|
|
175832
|
+
for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
|
|
175805
175833
|
if (seen.has(relPath)) {
|
|
175806
175834
|
continue;
|
|
175807
175835
|
}
|
|
@@ -175819,7 +175847,7 @@ function codeFileOwners(projectRoot, codeInclude) {
|
|
|
175819
175847
|
if (entry === undefined) {
|
|
175820
175848
|
continue;
|
|
175821
175849
|
}
|
|
175822
|
-
for (const relPath of
|
|
175850
|
+
for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
|
|
175823
175851
|
const existing = owners.get(relPath);
|
|
175824
175852
|
if (existing === undefined) {
|
|
175825
175853
|
owners.set(relPath, [language]);
|
|
@@ -175830,28 +175858,77 @@ function codeFileOwners(projectRoot, codeInclude) {
|
|
|
175830
175858
|
}
|
|
175831
175859
|
return owners;
|
|
175832
175860
|
}
|
|
175861
|
+
function collectLanguageFiles(projectRoot, language, patterns) {
|
|
175862
|
+
return collectFiles(projectRoot, patterns).filter((relPath) => !hasExcludedSuffix(language, relPath));
|
|
175863
|
+
}
|
|
175864
|
+
|
|
175865
|
+
// src/config/scanner-runtimes.ts
|
|
175866
|
+
var RUNTIME_WORKER_LANGUAGES = ["python", "ruby", "java"];
|
|
175867
|
+
var KNOWN_RUNTIME_ENTRY_KEYS = new Set(["command"]);
|
|
175868
|
+
function validateScannerRuntimes(value, diagnostics) {
|
|
175869
|
+
if (!isPlainObject(value)) {
|
|
175870
|
+
diagnostics.push(configDiagnostic("config_invalid_value", "scanners", "`scanners` must be an object keyed by runtime-backed language."));
|
|
175871
|
+
return {};
|
|
175872
|
+
}
|
|
175873
|
+
const result = {};
|
|
175874
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
175875
|
+
if (!isRuntimeWorkerLanguage(key)) {
|
|
175876
|
+
diagnostics.push(configDiagnostic("config_unknown_key", `scanners.${key}`, `Unknown scanner runtime: ${key}. Supported runtimes: ${RUNTIME_WORKER_LANGUAGES.join(", ")}.`));
|
|
175877
|
+
continue;
|
|
175878
|
+
}
|
|
175879
|
+
const command = validateRuntimeEntry(key, entry, diagnostics);
|
|
175880
|
+
if (command !== undefined) {
|
|
175881
|
+
result[key] = { command };
|
|
175882
|
+
}
|
|
175883
|
+
}
|
|
175884
|
+
return result;
|
|
175885
|
+
}
|
|
175886
|
+
function validateRuntimeEntry(language, value, diagnostics) {
|
|
175887
|
+
const target = `scanners.${language}`;
|
|
175888
|
+
if (!isPlainObject(value)) {
|
|
175889
|
+
diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`command\` array.`));
|
|
175890
|
+
return;
|
|
175891
|
+
}
|
|
175892
|
+
for (const key of Object.keys(value)) {
|
|
175893
|
+
if (!KNOWN_RUNTIME_ENTRY_KEYS.has(key)) {
|
|
175894
|
+
diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
|
|
175895
|
+
}
|
|
175896
|
+
}
|
|
175897
|
+
const command = value.command;
|
|
175898
|
+
if (!Array.isArray(command) || command.length === 0 || !command.every((part) => typeof part === "string" && part !== "")) {
|
|
175899
|
+
diagnostics.push(configDiagnostic("config_invalid_value", `${target}.command`, `\`${target}.command\` must be a non-empty array of non-empty strings: the runtime executable followed by its arguments.`));
|
|
175900
|
+
return;
|
|
175901
|
+
}
|
|
175902
|
+
return command;
|
|
175903
|
+
}
|
|
175904
|
+
function isRuntimeWorkerLanguage(value) {
|
|
175905
|
+
return RUNTIME_WORKER_LANGUAGES.includes(value);
|
|
175906
|
+
}
|
|
175907
|
+
function configDiagnostic(code, target, message) {
|
|
175908
|
+
return { severity: "error", code, target, message };
|
|
175909
|
+
}
|
|
175910
|
+
function isPlainObject(value) {
|
|
175911
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
175912
|
+
}
|
|
175833
175913
|
|
|
175834
175914
|
// src/config/config.ts
|
|
175835
175915
|
var CONFIG_FILE_NAME = "docbridge.config.json";
|
|
175836
175916
|
var EMPTY_CONFIG = {
|
|
175837
175917
|
include: { code: {}, docs: [] }
|
|
175838
175918
|
};
|
|
175839
|
-
var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
|
|
175919
|
+
var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include", "scanners"]);
|
|
175840
175920
|
var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
|
|
175841
175921
|
var KNOWN_CODE_ENTRY_KEYS = new Set(["patterns", "visibility"]);
|
|
175842
|
-
var LANGUAGE_SUFFIX = {
|
|
175843
|
-
typescript: ".ts",
|
|
175844
|
-
swift: ".swift",
|
|
175845
|
-
dart: ".dart",
|
|
175846
|
-
rust: ".rs",
|
|
175847
|
-
go: ".go"
|
|
175848
|
-
};
|
|
175849
175922
|
var LANGUAGE_VISIBILITY = {
|
|
175850
175923
|
typescript: ["public", "protected", "private"],
|
|
175851
175924
|
swift: ["public", "open", "internal"],
|
|
175852
175925
|
dart: ["public"],
|
|
175853
175926
|
rust: ["pub", "private"],
|
|
175854
|
-
go: ["exported", "unexported"]
|
|
175927
|
+
go: ["exported", "unexported"],
|
|
175928
|
+
javascript: ["public", "protected", "private"],
|
|
175929
|
+
python: ["public", "private"],
|
|
175930
|
+
ruby: ["public", "protected", "private"],
|
|
175931
|
+
java: ["public", "protected", "package", "private"]
|
|
175855
175932
|
};
|
|
175856
175933
|
function loadConfig(projectRoot) {
|
|
175857
175934
|
let rawText;
|
|
@@ -175880,7 +175957,7 @@ function resolveConfig(rawText) {
|
|
|
175880
175957
|
config: EMPTY_CONFIG,
|
|
175881
175958
|
ok: false,
|
|
175882
175959
|
diagnostics: [
|
|
175883
|
-
|
|
175960
|
+
configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
|
|
175884
175961
|
]
|
|
175885
175962
|
};
|
|
175886
175963
|
}
|
|
@@ -175893,57 +175970,61 @@ function resolveConfig(rawText) {
|
|
|
175893
175970
|
config: EMPTY_CONFIG,
|
|
175894
175971
|
ok: false,
|
|
175895
175972
|
diagnostics: [
|
|
175896
|
-
|
|
175973
|
+
configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
|
|
175897
175974
|
]
|
|
175898
175975
|
};
|
|
175899
175976
|
}
|
|
175900
175977
|
const diagnostics = [];
|
|
175901
|
-
if (!
|
|
175902
|
-
diagnostics.push(
|
|
175978
|
+
if (!isPlainObject2(parsed)) {
|
|
175979
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
|
|
175903
175980
|
return { config: EMPTY_CONFIG, diagnostics, ok: false };
|
|
175904
175981
|
}
|
|
175905
175982
|
for (const key of Object.keys(parsed)) {
|
|
175906
175983
|
if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
|
|
175907
|
-
diagnostics.push(
|
|
175984
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
|
|
175908
175985
|
}
|
|
175909
175986
|
}
|
|
175910
175987
|
if ("$schema" in parsed && typeof parsed.$schema !== "string") {
|
|
175911
|
-
diagnostics.push(
|
|
175988
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "$schema", "`$schema` must be a string."));
|
|
175912
175989
|
}
|
|
175990
|
+
const scanners = "scanners" in parsed ? validateScannerRuntimes(parsed.scanners, diagnostics) : undefined;
|
|
175913
175991
|
const include = parsed.include;
|
|
175914
|
-
if (!
|
|
175915
|
-
diagnostics.push(
|
|
175992
|
+
if (!isPlainObject2(include)) {
|
|
175993
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
|
|
175916
175994
|
return { config: EMPTY_CONFIG, diagnostics, ok: false };
|
|
175917
175995
|
}
|
|
175918
175996
|
for (const key of Object.keys(include)) {
|
|
175919
175997
|
if (!KNOWN_INCLUDE_KEYS.has(key)) {
|
|
175920
|
-
diagnostics.push(
|
|
175998
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
|
|
175921
175999
|
}
|
|
175922
176000
|
}
|
|
175923
176001
|
const code = validateCodeInclude(include.code, diagnostics);
|
|
175924
|
-
validatePatternArray(include.docs, "docs", ".md",
|
|
176002
|
+
validatePatternArray(include.docs, "docs", [".md"], [], diagnostics);
|
|
175925
176003
|
const ok = diagnostics.length === 0;
|
|
175926
|
-
const config = ok ? {
|
|
176004
|
+
const config = ok ? {
|
|
176005
|
+
include: { code, docs: include.docs },
|
|
176006
|
+
...scanners === undefined ? {} : { scanners }
|
|
176007
|
+
} : EMPTY_CONFIG;
|
|
175927
176008
|
return { config, diagnostics, ok };
|
|
175928
176009
|
}
|
|
175929
176010
|
function validateCodeInclude(value, diagnostics) {
|
|
175930
176011
|
if (Array.isArray(value)) {
|
|
175931
|
-
diagnostics.push(
|
|
176012
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", '`include.code` must be a language-keyed object such as `{ "typescript": { "patterns": ["src/**/*.ts"] } }`, not an array.'));
|
|
175932
176013
|
return {};
|
|
175933
176014
|
}
|
|
175934
|
-
if (!
|
|
175935
|
-
diagnostics.push(
|
|
176015
|
+
if (!isPlainObject2(value)) {
|
|
176016
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
|
|
175936
176017
|
return {};
|
|
175937
176018
|
}
|
|
175938
176019
|
const keys = Object.keys(value);
|
|
175939
176020
|
if (keys.length === 0) {
|
|
175940
|
-
diagnostics.push(
|
|
176021
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
|
|
175941
176022
|
return {};
|
|
175942
176023
|
}
|
|
175943
176024
|
const result = {};
|
|
175944
176025
|
for (const key of keys) {
|
|
175945
176026
|
if (!isCodeLanguage(key)) {
|
|
175946
|
-
diagnostics.push(
|
|
176027
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
|
|
175947
176028
|
continue;
|
|
175948
176029
|
}
|
|
175949
176030
|
const entry = validateCodeEntry(key, value[key], diagnostics);
|
|
@@ -175955,20 +176036,20 @@ function validateCodeInclude(value, diagnostics) {
|
|
|
175955
176036
|
}
|
|
175956
176037
|
function validateCodeEntry(language, value, diagnostics) {
|
|
175957
176038
|
const target = `include.code.${language}`;
|
|
175958
|
-
if (!
|
|
175959
|
-
diagnostics.push(
|
|
176039
|
+
if (!isPlainObject2(value)) {
|
|
176040
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
|
|
175960
176041
|
return;
|
|
175961
176042
|
}
|
|
175962
176043
|
for (const key of Object.keys(value)) {
|
|
175963
176044
|
if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
|
|
175964
|
-
diagnostics.push(
|
|
176045
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
|
|
175965
176046
|
}
|
|
175966
176047
|
}
|
|
175967
176048
|
const before = diagnostics.length;
|
|
175968
|
-
validatePatternArray(value.patterns, `code.${language}.patterns`,
|
|
176049
|
+
validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIXES[language], EXCLUDED_SUFFIXES[language], diagnostics);
|
|
175969
176050
|
if ("visibility" in value) {
|
|
175970
176051
|
if (!isStringArray(value.visibility)) {
|
|
175971
|
-
diagnostics.push(
|
|
176052
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
|
|
175972
176053
|
} else {
|
|
175973
176054
|
validateVisibilityOptions(language, value.visibility, target, diagnostics);
|
|
175974
176055
|
}
|
|
@@ -175985,47 +176066,49 @@ function validateCodeEntry(language, value, diagnostics) {
|
|
|
175985
176066
|
function validateVisibilityOptions(language, values, target, diagnostics) {
|
|
175986
176067
|
const allowed = LANGUAGE_VISIBILITY[language];
|
|
175987
176068
|
if (allowed.length === 0) {
|
|
175988
|
-
diagnostics.push(
|
|
176069
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
|
|
175989
176070
|
return;
|
|
175990
176071
|
}
|
|
175991
176072
|
for (const value of values) {
|
|
175992
176073
|
if (!allowed.includes(value)) {
|
|
175993
|
-
diagnostics.push(
|
|
176074
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
|
|
175994
176075
|
}
|
|
175995
176076
|
}
|
|
175996
176077
|
}
|
|
175997
|
-
function validatePatternArray(value, field,
|
|
176078
|
+
function validatePatternArray(value, field, suffixes, excludedSuffixes, diagnostics) {
|
|
175998
176079
|
const target = `include.${field}`;
|
|
175999
176080
|
if (!Array.isArray(value)) {
|
|
176000
|
-
diagnostics.push(
|
|
176081
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
|
|
176001
176082
|
return;
|
|
176002
176083
|
}
|
|
176003
176084
|
if (value.length === 0) {
|
|
176004
|
-
diagnostics.push(
|
|
176085
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
|
|
176005
176086
|
return;
|
|
176006
176087
|
}
|
|
176007
176088
|
for (const pattern of value) {
|
|
176008
176089
|
if (typeof pattern !== "string") {
|
|
176009
|
-
diagnostics.push(
|
|
176090
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
|
|
176010
176091
|
continue;
|
|
176011
176092
|
}
|
|
176012
176093
|
const globResult = validateGlobPattern(pattern);
|
|
176013
176094
|
if (!globResult.ok) {
|
|
176014
|
-
diagnostics.push(
|
|
176095
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
|
|
176015
176096
|
continue;
|
|
176016
176097
|
}
|
|
176017
|
-
const suffixError =
|
|
176098
|
+
const suffixError = checkPatternSuffix(pattern, suffixes, excludedSuffixes);
|
|
176018
176099
|
if (suffixError !== undefined) {
|
|
176019
|
-
diagnostics.push(
|
|
176100
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", pattern, suffixError));
|
|
176020
176101
|
}
|
|
176021
176102
|
}
|
|
176022
176103
|
}
|
|
176023
|
-
function
|
|
176024
|
-
if (!pattern.endsWith(
|
|
176025
|
-
|
|
176104
|
+
function checkPatternSuffix(pattern, suffixes, excludedSuffixes) {
|
|
176105
|
+
if (!suffixes.some((suffix) => pattern.endsWith(suffix))) {
|
|
176106
|
+
const listed = suffixes.map((suffix) => `\`${suffix}\``).join(", ");
|
|
176107
|
+
return suffixes.length === 1 ? `Pattern must end with ${listed}.` : `Pattern must end with one of ${listed}.`;
|
|
176026
176108
|
}
|
|
176027
|
-
|
|
176028
|
-
|
|
176109
|
+
const excluded = excludedSuffixes.find((suffix) => pattern.endsWith(suffix));
|
|
176110
|
+
if (excluded !== undefined) {
|
|
176111
|
+
return `Pattern must not target \`${excluded}\` declaration files.`;
|
|
176029
176112
|
}
|
|
176030
176113
|
return;
|
|
176031
176114
|
}
|
|
@@ -176033,15 +176116,15 @@ function detectLanguageOverlap(projectRoot, code) {
|
|
|
176033
176116
|
const diagnostics = [];
|
|
176034
176117
|
for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
|
|
176035
176118
|
if (owners.length > 1) {
|
|
176036
|
-
diagnostics.push(
|
|
176119
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", relPath, `Code file ${relPath} matches multiple configured languages (${owners.join(", ")}). Each code file must belong to exactly one language.`));
|
|
176037
176120
|
}
|
|
176038
176121
|
}
|
|
176039
176122
|
return diagnostics;
|
|
176040
176123
|
}
|
|
176041
|
-
function
|
|
176124
|
+
function configDiagnostic2(code, target, message) {
|
|
176042
176125
|
return { severity: "error", code, target, message };
|
|
176043
176126
|
}
|
|
176044
|
-
function
|
|
176127
|
+
function isPlainObject2(value) {
|
|
176045
176128
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
176046
176129
|
}
|
|
176047
176130
|
function isStringArray(value) {
|
|
@@ -176568,6 +176651,11 @@ function positionOf(range) {
|
|
|
176568
176651
|
// src/lsp/server.ts
|
|
176569
176652
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
176570
176653
|
|
|
176654
|
+
// src/shared/error.ts
|
|
176655
|
+
function reasonOf(error) {
|
|
176656
|
+
return error instanceof Error ? error.message : String(error);
|
|
176657
|
+
}
|
|
176658
|
+
|
|
176571
176659
|
// src/lsp/position.ts
|
|
176572
176660
|
function toLspPosition(position) {
|
|
176573
176661
|
return { line: position.line - 1, character: position.column - 1 };
|
|
@@ -176951,32 +177039,27 @@ function renderCodeSignatures(state, counterparts) {
|
|
|
176951
177039
|
const signature = content === undefined || symbol.signatureRange === undefined ? "" : withoutLeadingComments(sliceSourceRange(content, symbol.signatureRange).content, symbol.language).trimEnd();
|
|
176952
177040
|
const fenced = signature.length > 0 ? `
|
|
176953
177041
|
|
|
176954
|
-
\`\`\`${
|
|
177042
|
+
\`\`\`${codeFenceLanguage(symbol.language, symbol.filePath)}
|
|
176955
177043
|
${signature}
|
|
176956
177044
|
\`\`\`` : "";
|
|
176957
177045
|
blocks.push(`**${symbol.endpoint}**${fenced}`);
|
|
176958
177046
|
}
|
|
176959
177047
|
return blocks.length > 0 ? blocks.join(DIVIDER) : null;
|
|
176960
177048
|
}
|
|
176961
|
-
var FENCE_LANGUAGE = {
|
|
176962
|
-
typescript: "ts",
|
|
176963
|
-
swift: "swift",
|
|
176964
|
-
dart: "dart",
|
|
176965
|
-
rust: "rust",
|
|
176966
|
-
go: "go"
|
|
176967
|
-
};
|
|
176968
177049
|
var NESTED_BLOCK_COMMENTS = new Set(["swift", "dart", "rust"]);
|
|
177050
|
+
var HASH_LINE_COMMENTS = new Set(["python", "ruby"]);
|
|
176969
177051
|
function withoutLeadingComments(text, language) {
|
|
177052
|
+
const lineComment = HASH_LINE_COMMENTS.has(language) ? "#" : "//";
|
|
176970
177053
|
let index = 0;
|
|
176971
177054
|
for (;; ) {
|
|
176972
177055
|
while (index < text.length && /\s/.test(text[index] ?? "")) {
|
|
176973
177056
|
index += 1;
|
|
176974
177057
|
}
|
|
176975
|
-
if (text.startsWith(
|
|
177058
|
+
if (text.startsWith(lineComment, index)) {
|
|
176976
177059
|
const newline = text.indexOf(`
|
|
176977
177060
|
`, index);
|
|
176978
177061
|
index = newline === -1 ? text.length : newline + 1;
|
|
176979
|
-
} else if (text.startsWith("/*", index)) {
|
|
177062
|
+
} else if (lineComment === "//" && text.startsWith("/*", index)) {
|
|
176980
177063
|
index = blockCommentEnd(text, index, NESTED_BLOCK_COMMENTS.has(language));
|
|
176981
177064
|
} else {
|
|
176982
177065
|
return text.slice(index);
|
|
@@ -177517,24 +177600,205 @@ function manifestLink(source, target, entry, side) {
|
|
|
177517
177600
|
};
|
|
177518
177601
|
}
|
|
177519
177602
|
|
|
177603
|
+
// src/shared/cancelable.ts
|
|
177604
|
+
function deferred() {
|
|
177605
|
+
let settle;
|
|
177606
|
+
const promise = new Promise((resolve2, reject) => {
|
|
177607
|
+
settle = { resolve: resolve2, reject };
|
|
177608
|
+
});
|
|
177609
|
+
return {
|
|
177610
|
+
promise,
|
|
177611
|
+
resolve: (value) => settle?.resolve(value),
|
|
177612
|
+
reject: (error) => settle?.reject(error)
|
|
177613
|
+
};
|
|
177614
|
+
}
|
|
177615
|
+
function settledCancelable(value) {
|
|
177616
|
+
return { promise: Promise.resolve(value), cancel: () => {
|
|
177617
|
+
return;
|
|
177618
|
+
} };
|
|
177619
|
+
}
|
|
177620
|
+
function mapCancelable(task, map) {
|
|
177621
|
+
const mapped = deferred();
|
|
177622
|
+
let cancelled = false;
|
|
177623
|
+
task.promise.then((value) => {
|
|
177624
|
+
if (cancelled) {
|
|
177625
|
+
throw abortError();
|
|
177626
|
+
}
|
|
177627
|
+
return map(value);
|
|
177628
|
+
}).then(mapped.resolve, mapped.reject);
|
|
177629
|
+
return {
|
|
177630
|
+
promise: mapped.promise,
|
|
177631
|
+
cancel() {
|
|
177632
|
+
cancelled = true;
|
|
177633
|
+
mapped.reject(abortError());
|
|
177634
|
+
task.cancel();
|
|
177635
|
+
}
|
|
177636
|
+
};
|
|
177637
|
+
}
|
|
177638
|
+
function abortError() {
|
|
177639
|
+
const error = new Error("The operation was cancelled");
|
|
177640
|
+
error.name = "AbortError";
|
|
177641
|
+
return error;
|
|
177642
|
+
}
|
|
177643
|
+
function isAbortError(error) {
|
|
177644
|
+
return error instanceof Error && error.name === "AbortError";
|
|
177645
|
+
}
|
|
177646
|
+
function cancelableSequence(body) {
|
|
177647
|
+
const outcome = deferred();
|
|
177648
|
+
let cancelled = false;
|
|
177649
|
+
let running;
|
|
177650
|
+
const step = async (next) => {
|
|
177651
|
+
if (cancelled) {
|
|
177652
|
+
if (typeof next !== "function") {
|
|
177653
|
+
next.promise.catch(() => {
|
|
177654
|
+
return;
|
|
177655
|
+
});
|
|
177656
|
+
next.cancel();
|
|
177657
|
+
}
|
|
177658
|
+
throw abortError();
|
|
177659
|
+
}
|
|
177660
|
+
const task = typeof next === "function" ? next() : next;
|
|
177661
|
+
running = task;
|
|
177662
|
+
try {
|
|
177663
|
+
return await task.promise;
|
|
177664
|
+
} finally {
|
|
177665
|
+
if (running === task) {
|
|
177666
|
+
running = undefined;
|
|
177667
|
+
}
|
|
177668
|
+
}
|
|
177669
|
+
};
|
|
177670
|
+
(async () => body(step))().then(outcome.resolve, outcome.reject);
|
|
177671
|
+
return {
|
|
177672
|
+
promise: outcome.promise,
|
|
177673
|
+
cancel() {
|
|
177674
|
+
if (cancelled) {
|
|
177675
|
+
return;
|
|
177676
|
+
}
|
|
177677
|
+
cancelled = true;
|
|
177678
|
+
outcome.reject(abortError());
|
|
177679
|
+
running?.cancel();
|
|
177680
|
+
}
|
|
177681
|
+
};
|
|
177682
|
+
}
|
|
177683
|
+
|
|
177684
|
+
// src/scan/code/scan-cache.ts
|
|
177685
|
+
import { createHash } from "node:crypto";
|
|
177686
|
+
function emptyCodeScanCache() {
|
|
177687
|
+
return { fingerprint: "", entries: new Map };
|
|
177688
|
+
}
|
|
177689
|
+
function codeScanCacheKey(language, filePath, content, options, argv, runtime = []) {
|
|
177690
|
+
const contentHash = createHash("sha256").update(content).digest("hex");
|
|
177691
|
+
return JSON.stringify([
|
|
177692
|
+
language,
|
|
177693
|
+
filePath,
|
|
177694
|
+
contentHash,
|
|
177695
|
+
options.visibility ?? null,
|
|
177696
|
+
argv,
|
|
177697
|
+
runtime
|
|
177698
|
+
]);
|
|
177699
|
+
}
|
|
177700
|
+
function isReusableScan(scan) {
|
|
177701
|
+
return !scan.diagnostics.some((diagnostic) => diagnostic.code === "code_scanner_unavailable" || diagnostic.code === "code_scanner_failed");
|
|
177702
|
+
}
|
|
177703
|
+
|
|
177520
177704
|
// src/scan/code/typescript.ts
|
|
177705
|
+
var import_typescript2 = __toESM(require_typescript(), 1);
|
|
177706
|
+
|
|
177707
|
+
// src/scan/code/typescript-diagnostics.ts
|
|
177521
177708
|
var import_typescript = __toESM(require_typescript(), 1);
|
|
177522
|
-
|
|
177709
|
+
function scriptLanguage(filePath) {
|
|
177710
|
+
return LANGUAGE_SUFFIXES.javascript.some((suffix) => filePath.endsWith(suffix)) ? "javascript" : "typescript";
|
|
177711
|
+
}
|
|
177712
|
+
var LABEL = {
|
|
177713
|
+
typescript: "TypeScript",
|
|
177714
|
+
javascript: "JavaScript"
|
|
177715
|
+
};
|
|
177716
|
+
var SUPPORTED_DECLARATIONS = {
|
|
177717
|
+
typescript: "Supported declarations are top-level exported function, class, interface, type, single-declarator const, enum, and named default function or class, plus the identifier-named members of a class, interface, or object type alias.",
|
|
177718
|
+
javascript: "Supported declarations are top-level ESM-exported function, class, single-declarator const, let, or var, and named default function or class, plus the identifier-named members of a class. CommonJS assignments are not declarations."
|
|
177719
|
+
};
|
|
177720
|
+
function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
|
|
177721
|
+
const location = { filePath, line: 1, column: 1 };
|
|
177722
|
+
if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
|
|
177723
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
|
|
177724
|
+
location.line = line + 1;
|
|
177725
|
+
location.column = character + 1;
|
|
177726
|
+
}
|
|
177727
|
+
const language = scriptLanguage(filePath);
|
|
177728
|
+
const detail = diagnostic === undefined ? `${LABEL[language]} file has a syntactic parse error.` : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
177729
|
+
`);
|
|
177730
|
+
return {
|
|
177731
|
+
severity: "error",
|
|
177732
|
+
code: "code_parse_error",
|
|
177733
|
+
language,
|
|
177734
|
+
target: filePath,
|
|
177735
|
+
message: `${LABEL[language]} parse error: ${detail}`,
|
|
177736
|
+
location
|
|
177737
|
+
};
|
|
177738
|
+
}
|
|
177739
|
+
function unsupportedDeclarationDiagnostic(filePath, location) {
|
|
177740
|
+
const language = scriptLanguage(filePath);
|
|
177741
|
+
return {
|
|
177742
|
+
severity: "warning",
|
|
177743
|
+
code: "unsupported_declaration",
|
|
177744
|
+
language,
|
|
177745
|
+
target: filePath,
|
|
177746
|
+
message: `@doc is attached to an unsupported declaration. ${SUPPORTED_DECLARATIONS[language]}`,
|
|
177747
|
+
location
|
|
177748
|
+
};
|
|
177749
|
+
}
|
|
177750
|
+
function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
|
|
177751
|
+
const diagnostic = {
|
|
177752
|
+
severity: "error",
|
|
177753
|
+
code: "duplicate_code_symbol",
|
|
177754
|
+
language: scriptLanguage(location.filePath),
|
|
177755
|
+
target: endpoint,
|
|
177756
|
+
message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
|
|
177757
|
+
location
|
|
177758
|
+
};
|
|
177759
|
+
if (range !== undefined) {
|
|
177760
|
+
diagnostic.range = range;
|
|
177761
|
+
}
|
|
177762
|
+
return diagnostic;
|
|
177763
|
+
}
|
|
177764
|
+
function duplicateLinkDiagnostic(source, target, location, range) {
|
|
177765
|
+
const diagnostic = {
|
|
177766
|
+
severity: "warning",
|
|
177767
|
+
code: "duplicate_link",
|
|
177768
|
+
target,
|
|
177769
|
+
source,
|
|
177770
|
+
message: `Duplicate @doc link from ${source} to ${target}.`,
|
|
177771
|
+
location
|
|
177772
|
+
};
|
|
177773
|
+
if (range !== undefined) {
|
|
177774
|
+
diagnostic.range = range;
|
|
177775
|
+
}
|
|
177776
|
+
return diagnostic;
|
|
177777
|
+
}
|
|
177778
|
+
|
|
177779
|
+
// src/scan/code/typescript.ts
|
|
177523
177780
|
var typeScriptAdapter = {
|
|
177524
|
-
language:
|
|
177781
|
+
language: "typescript",
|
|
177525
177782
|
scanFile(filePath, content, options) {
|
|
177526
177783
|
return scanTypeScript(filePath, content, options);
|
|
177784
|
+
},
|
|
177785
|
+
scanFiles(files, options) {
|
|
177786
|
+
return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
|
|
177527
177787
|
}
|
|
177528
177788
|
};
|
|
177789
|
+
var javaScriptAdapter = {
|
|
177790
|
+
...typeScriptAdapter,
|
|
177791
|
+
language: "javascript"
|
|
177792
|
+
};
|
|
177529
177793
|
var DEFAULT_MEMBER_VISIBILITY = ["public", "protected"];
|
|
177530
177794
|
function scanTypeScript(filePath, content, options = {}) {
|
|
177531
177795
|
const visibility = new Set(options.visibility ?? DEFAULT_MEMBER_VISIBILITY);
|
|
177532
|
-
const sourceFile =
|
|
177533
|
-
const
|
|
177534
|
-
if (
|
|
177535
|
-
const first =
|
|
177796
|
+
const sourceFile = import_typescript2.default.createSourceFile(filePath, content, import_typescript2.default.ScriptTarget.Latest, true, scriptKindOf(filePath));
|
|
177797
|
+
const syntaxErrors = syntaxDiagnostics(sourceFile);
|
|
177798
|
+
if (syntaxErrors.length > 0) {
|
|
177799
|
+
const first = syntaxErrors[0];
|
|
177536
177800
|
return {
|
|
177537
|
-
language:
|
|
177801
|
+
language: scriptLanguage(filePath),
|
|
177538
177802
|
filePath,
|
|
177539
177803
|
symbols: [],
|
|
177540
177804
|
undocumentedSymbols: [],
|
|
@@ -177604,11 +177868,13 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177604
177868
|
}
|
|
177605
177869
|
const parsed = parseLinkTarget(docTag.rawTarget, parseOptions);
|
|
177606
177870
|
if (!parsed.ok) {
|
|
177607
|
-
diagnostics.push(parsed.diagnostic);
|
|
177871
|
+
diagnostics.push(linkDiagnostic(filePath, parsed.diagnostic));
|
|
177608
177872
|
continue;
|
|
177609
177873
|
}
|
|
177610
177874
|
if (linkTargetsSeen.has(docTag.rawTarget)) {
|
|
177611
|
-
|
|
177875
|
+
const { rawTarget, location, targetRange } = docTag;
|
|
177876
|
+
const duplicate = duplicateLinkDiagnostic(endpoint, rawTarget, location, targetRange);
|
|
177877
|
+
diagnostics.push(linkDiagnostic(filePath, duplicate));
|
|
177612
177878
|
continue;
|
|
177613
177879
|
}
|
|
177614
177880
|
linkTargetsSeen.add(docTag.rawTarget);
|
|
@@ -177624,7 +177890,7 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177624
177890
|
}
|
|
177625
177891
|
}
|
|
177626
177892
|
return {
|
|
177627
|
-
language:
|
|
177893
|
+
language: scriptLanguage(filePath),
|
|
177628
177894
|
filePath,
|
|
177629
177895
|
symbols,
|
|
177630
177896
|
undocumentedSymbols,
|
|
@@ -177632,15 +177898,60 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177632
177898
|
diagnostics
|
|
177633
177899
|
};
|
|
177634
177900
|
}
|
|
177901
|
+
function linkDiagnostic(filePath, diagnostic) {
|
|
177902
|
+
if (scriptLanguage(filePath) !== "javascript") {
|
|
177903
|
+
return diagnostic;
|
|
177904
|
+
}
|
|
177905
|
+
const { severity, code, ...rest } = diagnostic;
|
|
177906
|
+
return { severity, code, language: "javascript", ...rest };
|
|
177907
|
+
}
|
|
177908
|
+
function scriptKindOf(filePath) {
|
|
177909
|
+
if (scriptLanguage(filePath) === "javascript") {
|
|
177910
|
+
return filePath.endsWith(".jsx") ? import_typescript2.default.ScriptKind.JSX : import_typescript2.default.ScriptKind.JS;
|
|
177911
|
+
}
|
|
177912
|
+
return filePath.endsWith(".tsx") ? import_typescript2.default.ScriptKind.TSX : import_typescript2.default.ScriptKind.TS;
|
|
177913
|
+
}
|
|
177914
|
+
function syntaxDiagnostics(sourceFile) {
|
|
177915
|
+
const parseDiagnostics = getParseDiagnostics(sourceFile);
|
|
177916
|
+
if (parseDiagnostics.length > 0 || scriptLanguage(sourceFile.fileName) !== "javascript") {
|
|
177917
|
+
return parseDiagnostics;
|
|
177918
|
+
}
|
|
177919
|
+
return javaScriptGrammarDiagnostics(sourceFile);
|
|
177920
|
+
}
|
|
177635
177921
|
function getParseDiagnostics(sourceFile) {
|
|
177636
177922
|
const withDiagnostics = sourceFile;
|
|
177637
177923
|
return withDiagnostics.parseDiagnostics ?? [];
|
|
177638
177924
|
}
|
|
177925
|
+
function javaScriptGrammarDiagnostics(sourceFile) {
|
|
177926
|
+
const { fileName } = sourceFile;
|
|
177927
|
+
const host = {
|
|
177928
|
+
getSourceFile: (name) => name === fileName ? sourceFile : undefined,
|
|
177929
|
+
fileExists: (name) => name === fileName,
|
|
177930
|
+
readFile: () => {
|
|
177931
|
+
return;
|
|
177932
|
+
},
|
|
177933
|
+
writeFile: () => {
|
|
177934
|
+
return;
|
|
177935
|
+
},
|
|
177936
|
+
getDefaultLibFileName: () => "lib.d.ts",
|
|
177937
|
+
getCurrentDirectory: () => "/",
|
|
177938
|
+
getCanonicalFileName: (name) => name,
|
|
177939
|
+
useCaseSensitiveFileNames: () => true,
|
|
177940
|
+
getNewLine: () => `
|
|
177941
|
+
`
|
|
177942
|
+
};
|
|
177943
|
+
const program = import_typescript2.default.createProgram({
|
|
177944
|
+
rootNames: [fileName],
|
|
177945
|
+
options: { allowJs: true, noLib: true, noResolve: true, types: [] },
|
|
177946
|
+
host
|
|
177947
|
+
});
|
|
177948
|
+
return program.getSyntacticDiagnostics(sourceFile);
|
|
177949
|
+
}
|
|
177639
177950
|
function collectDocTags(filePath, sourceFile, statement) {
|
|
177640
177951
|
const node = jsDocCarrier(statement);
|
|
177641
177952
|
const location = locationOf(filePath, sourceFile, statement);
|
|
177642
177953
|
const tags = [];
|
|
177643
|
-
for (const tag of
|
|
177954
|
+
for (const tag of import_typescript2.default.getJSDocTags(node)) {
|
|
177644
177955
|
if (tag.tagName.escapedText !== "doc") {
|
|
177645
177956
|
continue;
|
|
177646
177957
|
}
|
|
@@ -177659,7 +177970,7 @@ function collectDocTags(filePath, sourceFile, statement) {
|
|
|
177659
177970
|
return tags;
|
|
177660
177971
|
}
|
|
177661
177972
|
function jsDocCarrier(statement) {
|
|
177662
|
-
if (
|
|
177973
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177663
177974
|
const declaration = statement.declarationList.declarations[0];
|
|
177664
177975
|
if (declaration !== undefined) {
|
|
177665
177976
|
return declaration;
|
|
@@ -177705,11 +178016,11 @@ function collectMemberDeclarations(filePath, sourceFile, statement, visibility,
|
|
|
177705
178016
|
}
|
|
177706
178017
|
}
|
|
177707
178018
|
function diagnoseParameterProperties(filePath, sourceFile, member, diagnostics) {
|
|
177708
|
-
if (!
|
|
178019
|
+
if (!import_typescript2.default.isConstructorDeclaration(member)) {
|
|
177709
178020
|
return;
|
|
177710
178021
|
}
|
|
177711
178022
|
for (const parameter of member.parameters) {
|
|
177712
|
-
if (!
|
|
178023
|
+
if (!import_typescript2.default.isParameterPropertyDeclaration(parameter, member)) {
|
|
177713
178024
|
continue;
|
|
177714
178025
|
}
|
|
177715
178026
|
if (collectDocTags(filePath, sourceFile, parameter).length > 0) {
|
|
@@ -177725,22 +178036,22 @@ function describeContainer(statement) {
|
|
|
177725
178036
|
return { name: supportedNameNode(statement)?.text ?? null, members };
|
|
177726
178037
|
}
|
|
177727
178038
|
function containerMembers(statement) {
|
|
177728
|
-
if (
|
|
178039
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177729
178040
|
return statement.members;
|
|
177730
178041
|
}
|
|
177731
|
-
if (
|
|
178042
|
+
if (import_typescript2.default.isInterfaceDeclaration(statement)) {
|
|
177732
178043
|
return statement.members;
|
|
177733
178044
|
}
|
|
177734
|
-
if (
|
|
178045
|
+
if (import_typescript2.default.isEnumDeclaration(statement)) {
|
|
177735
178046
|
return statement.members;
|
|
177736
178047
|
}
|
|
177737
|
-
if (
|
|
177738
|
-
return
|
|
178048
|
+
if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
|
|
178049
|
+
return import_typescript2.default.isTypeLiteralNode(statement.type) ? statement.type.members : null;
|
|
177739
178050
|
}
|
|
177740
|
-
if (
|
|
178051
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177741
178052
|
const declarations = statement.declarationList.declarations;
|
|
177742
178053
|
const initializer = declarations.length === 1 ? declarations[0]?.initializer : undefined;
|
|
177743
|
-
return initializer !== undefined &&
|
|
178054
|
+
return initializer !== undefined && import_typescript2.default.isClassExpression(initializer) ? initializer.members : null;
|
|
177744
178055
|
}
|
|
177745
178056
|
return null;
|
|
177746
178057
|
}
|
|
@@ -177762,24 +178073,24 @@ function describeMember(filePath, sourceFile, containerName, member, docTags) {
|
|
|
177762
178073
|
};
|
|
177763
178074
|
}
|
|
177764
178075
|
function isVisibleMember(member, visibility) {
|
|
177765
|
-
if (hasModifier(member,
|
|
178076
|
+
if (hasModifier(member, import_typescript2.default.SyntaxKind.PrivateKeyword)) {
|
|
177766
178077
|
return visibility.has("private");
|
|
177767
178078
|
}
|
|
177768
|
-
if (hasModifier(member,
|
|
178079
|
+
if (hasModifier(member, import_typescript2.default.SyntaxKind.ProtectedKeyword)) {
|
|
177769
178080
|
return visibility.has("protected");
|
|
177770
178081
|
}
|
|
177771
178082
|
return visibility.has("public");
|
|
177772
178083
|
}
|
|
177773
178084
|
function memberIdentity(sourceFile, member) {
|
|
177774
|
-
if (
|
|
177775
|
-
const keyword = member.getChildren(sourceFile).find((child) => child.kind ===
|
|
178085
|
+
if (import_typescript2.default.isConstructorDeclaration(member)) {
|
|
178086
|
+
const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript2.default.SyntaxKind.ConstructorKeyword);
|
|
177776
178087
|
return keyword === undefined ? null : { name: "constructor", nameRange: rangeOfNode(sourceFile, keyword) };
|
|
177777
178088
|
}
|
|
177778
|
-
if (!
|
|
178089
|
+
if (!import_typescript2.default.isMethodDeclaration(member) && !import_typescript2.default.isPropertyDeclaration(member) && !import_typescript2.default.isGetAccessorDeclaration(member) && !import_typescript2.default.isSetAccessorDeclaration(member) && !import_typescript2.default.isMethodSignature(member) && !import_typescript2.default.isPropertySignature(member)) {
|
|
177779
178090
|
return null;
|
|
177780
178091
|
}
|
|
177781
178092
|
const name = member.name;
|
|
177782
|
-
return
|
|
178093
|
+
return import_typescript2.default.isIdentifier(name) ? { name: name.text, nameRange: rangeOfNode(sourceFile, name) } : null;
|
|
177783
178094
|
}
|
|
177784
178095
|
function memberSignatureEndOffset(sourceFile, member) {
|
|
177785
178096
|
const body = member.body;
|
|
@@ -177788,28 +178099,28 @@ function memberSignatureEndOffset(sourceFile, member) {
|
|
|
177788
178099
|
function supportedNameNode(statement) {
|
|
177789
178100
|
const isExported = hasExportModifier(statement);
|
|
177790
178101
|
const isDefault = hasDefaultModifier(statement);
|
|
177791
|
-
if (
|
|
178102
|
+
if (import_typescript2.default.isFunctionDeclaration(statement)) {
|
|
177792
178103
|
if (!isExported || statement.name === undefined) {
|
|
177793
178104
|
return null;
|
|
177794
178105
|
}
|
|
177795
178106
|
return statement.name;
|
|
177796
178107
|
}
|
|
177797
|
-
if (
|
|
178108
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177798
178109
|
if (!isExported || statement.name === undefined) {
|
|
177799
178110
|
return null;
|
|
177800
178111
|
}
|
|
177801
178112
|
return statement.name;
|
|
177802
178113
|
}
|
|
177803
|
-
if (
|
|
178114
|
+
if (import_typescript2.default.isInterfaceDeclaration(statement)) {
|
|
177804
178115
|
return isExported && !isDefault ? statement.name : null;
|
|
177805
178116
|
}
|
|
177806
|
-
if (
|
|
178117
|
+
if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
|
|
177807
178118
|
return isExported && !isDefault ? statement.name : null;
|
|
177808
178119
|
}
|
|
177809
|
-
if (
|
|
178120
|
+
if (import_typescript2.default.isEnumDeclaration(statement)) {
|
|
177810
178121
|
return isExported && !isDefault ? statement.name : null;
|
|
177811
178122
|
}
|
|
177812
|
-
if (
|
|
178123
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177813
178124
|
if (!isExported) {
|
|
177814
178125
|
return null;
|
|
177815
178126
|
}
|
|
@@ -177819,23 +178130,23 @@ function supportedNameNode(statement) {
|
|
|
177819
178130
|
return null;
|
|
177820
178131
|
}
|
|
177821
178132
|
const name = declaration.name;
|
|
177822
|
-
return
|
|
178133
|
+
return import_typescript2.default.isIdentifier(name) ? name : null;
|
|
177823
178134
|
}
|
|
177824
178135
|
return null;
|
|
177825
178136
|
}
|
|
177826
178137
|
function hasExportModifier(statement) {
|
|
177827
|
-
return hasModifier(statement,
|
|
178138
|
+
return hasModifier(statement, import_typescript2.default.SyntaxKind.ExportKeyword);
|
|
177828
178139
|
}
|
|
177829
178140
|
function hasDefaultModifier(statement) {
|
|
177830
|
-
return hasModifier(statement,
|
|
178141
|
+
return hasModifier(statement, import_typescript2.default.SyntaxKind.DefaultKeyword);
|
|
177831
178142
|
}
|
|
177832
178143
|
function hasModifier(node, kind) {
|
|
177833
|
-
const modifiers =
|
|
178144
|
+
const modifiers = import_typescript2.default.canHaveModifiers(node) ? import_typescript2.default.getModifiers(node) : undefined;
|
|
177834
178145
|
return modifiers?.some((modifier) => modifier.kind === kind) ?? false;
|
|
177835
178146
|
}
|
|
177836
178147
|
function declarationLocation(filePath, sourceFile, node) {
|
|
177837
|
-
const carrier =
|
|
177838
|
-
const name = carrier === undefined ? undefined :
|
|
178148
|
+
const carrier = import_typescript2.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
|
|
178149
|
+
const name = carrier === undefined ? undefined : import_typescript2.default.getNameOfDeclaration(carrier);
|
|
177839
178150
|
return locationOf(filePath, sourceFile, name ?? node);
|
|
177840
178151
|
}
|
|
177841
178152
|
function locationOf(filePath, sourceFile, node) {
|
|
@@ -177854,13 +178165,13 @@ function rangeOfNode(sourceFile, node) {
|
|
|
177854
178165
|
return rangeFromOffsets(sourceFile, node.getStart(sourceFile), node.getEnd());
|
|
177855
178166
|
}
|
|
177856
178167
|
function signatureEndOffset(sourceFile, statement) {
|
|
177857
|
-
if (
|
|
178168
|
+
if (import_typescript2.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
|
|
177858
178169
|
return statement.body.getStart(sourceFile);
|
|
177859
178170
|
}
|
|
177860
|
-
if (
|
|
178171
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177861
178172
|
return classBodyStartOffset(sourceFile, statement) ?? statement.getEnd();
|
|
177862
178173
|
}
|
|
177863
|
-
if (
|
|
178174
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177864
178175
|
return variableSignatureEndOffset(sourceFile, statement) ?? statement.getEnd();
|
|
177865
178176
|
}
|
|
177866
178177
|
return statement.getEnd();
|
|
@@ -177876,13 +178187,13 @@ function variableSignatureEndOffset(sourceFile, statement) {
|
|
|
177876
178187
|
if (initializer === undefined) {
|
|
177877
178188
|
return;
|
|
177878
178189
|
}
|
|
177879
|
-
if ((
|
|
178190
|
+
if ((import_typescript2.default.isArrowFunction(initializer) || import_typescript2.default.isFunctionExpression(initializer)) && import_typescript2.default.isBlock(initializer.body)) {
|
|
177880
178191
|
return initializer.body.getStart(sourceFile);
|
|
177881
178192
|
}
|
|
177882
|
-
if (
|
|
178193
|
+
if (import_typescript2.default.isObjectLiteralExpression(initializer)) {
|
|
177883
178194
|
return initializer.getStart(sourceFile);
|
|
177884
178195
|
}
|
|
177885
|
-
if (
|
|
178196
|
+
if (import_typescript2.default.isClassExpression(initializer)) {
|
|
177886
178197
|
return classBodyStartOffset(sourceFile, initializer);
|
|
177887
178198
|
}
|
|
177888
178199
|
return;
|
|
@@ -177897,7 +178208,7 @@ function targetRangeOf(sourceFile, tag, rawTarget) {
|
|
|
177897
178208
|
function makeCodeSymbol(filePath, endpoint, declaration) {
|
|
177898
178209
|
const symbol = {
|
|
177899
178210
|
kind: "code",
|
|
177900
|
-
language:
|
|
178211
|
+
language: scriptLanguage(filePath),
|
|
177901
178212
|
filePath,
|
|
177902
178213
|
symbolName: declaration.symbolName,
|
|
177903
178214
|
canonicalId: declaration.canonicalId,
|
|
@@ -177934,224 +178245,20 @@ function firstToken(text) {
|
|
|
177934
178245
|
}
|
|
177935
178246
|
return trimmed.split(/\s+/)[0];
|
|
177936
178247
|
}
|
|
177937
|
-
function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
|
|
177938
|
-
const location = { filePath, line: 1, column: 1 };
|
|
177939
|
-
if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
|
|
177940
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
|
|
177941
|
-
location.line = line + 1;
|
|
177942
|
-
location.column = character + 1;
|
|
177943
|
-
}
|
|
177944
|
-
const detail = diagnostic === undefined ? "TypeScript file has a syntactic parse error." : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
177945
|
-
`);
|
|
177946
|
-
return {
|
|
177947
|
-
severity: "error",
|
|
177948
|
-
code: "code_parse_error",
|
|
177949
|
-
language: LANGUAGE,
|
|
177950
|
-
target: filePath,
|
|
177951
|
-
message: `TypeScript parse error: ${detail}`,
|
|
177952
|
-
location
|
|
177953
|
-
};
|
|
177954
|
-
}
|
|
177955
|
-
function unsupportedDeclarationDiagnostic(filePath, location) {
|
|
177956
|
-
return {
|
|
177957
|
-
severity: "warning",
|
|
177958
|
-
code: "unsupported_declaration",
|
|
177959
|
-
language: LANGUAGE,
|
|
177960
|
-
target: filePath,
|
|
177961
|
-
message: "@doc is attached to an unsupported declaration. Supported declarations are top-level exported function, class, interface, type, single-declarator const, enum, and named default function or class, plus the identifier-named members of a class, interface, or object type alias.",
|
|
177962
|
-
location
|
|
177963
|
-
};
|
|
177964
|
-
}
|
|
177965
|
-
function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
|
|
177966
|
-
const diagnostic = {
|
|
177967
|
-
severity: "error",
|
|
177968
|
-
code: "duplicate_code_symbol",
|
|
177969
|
-
language: LANGUAGE,
|
|
177970
|
-
target: endpoint,
|
|
177971
|
-
message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
|
|
177972
|
-
location
|
|
177973
|
-
};
|
|
177974
|
-
if (range !== undefined) {
|
|
177975
|
-
diagnostic.range = range;
|
|
177976
|
-
}
|
|
177977
|
-
return diagnostic;
|
|
177978
|
-
}
|
|
177979
|
-
function duplicateLinkDiagnostic(source, target, location, range) {
|
|
177980
|
-
const diagnostic = {
|
|
177981
|
-
severity: "warning",
|
|
177982
|
-
code: "duplicate_link",
|
|
177983
|
-
target,
|
|
177984
|
-
source,
|
|
177985
|
-
message: `Duplicate @doc link from ${source} to ${target}.`,
|
|
177986
|
-
location
|
|
177987
|
-
};
|
|
177988
|
-
if (range !== undefined) {
|
|
177989
|
-
diagnostic.range = range;
|
|
177990
|
-
}
|
|
177991
|
-
return diagnostic;
|
|
177992
|
-
}
|
|
177993
178248
|
|
|
177994
|
-
// src/scan/code/worker/
|
|
177995
|
-
import { accessSync
|
|
177996
|
-
import { join as
|
|
178249
|
+
// src/scan/code/worker/runtime-worker.ts
|
|
178250
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
|
|
178251
|
+
import { extname, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
|
|
177997
178252
|
|
|
177998
|
-
// src/
|
|
177999
|
-
|
|
178000
|
-
return error instanceof Error ? error.message : String(error);
|
|
178001
|
-
}
|
|
178253
|
+
// src/scan/code/worker/runtime-probe.ts
|
|
178254
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
178002
178255
|
|
|
178003
|
-
// src/
|
|
178004
|
-
|
|
178256
|
+
// src/scan/code/worker/scanner-worker.ts
|
|
178257
|
+
var import__2020 = __toESM(require_2020(), 1);
|
|
178258
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
178259
|
+
import { mkdirSync } from "node:fs";
|
|
178260
|
+
import { tmpdir } from "node:os";
|
|
178005
178261
|
import { dirname, join as join4 } from "node:path";
|
|
178006
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
178007
|
-
function resolvePackageRoot(moduleUrl = import.meta.url) {
|
|
178008
|
-
const modulePath = fileURLToPath2(moduleUrl);
|
|
178009
|
-
let resolved;
|
|
178010
|
-
try {
|
|
178011
|
-
resolved = realpathSync(modulePath);
|
|
178012
|
-
} catch {
|
|
178013
|
-
resolved = modulePath;
|
|
178014
|
-
}
|
|
178015
|
-
let dir = dirname(resolved);
|
|
178016
|
-
for (;; ) {
|
|
178017
|
-
if (existsSync(join4(dir, "templates", "skills"))) {
|
|
178018
|
-
return dir;
|
|
178019
|
-
}
|
|
178020
|
-
const parent = dirname(dir);
|
|
178021
|
-
if (parent === dir) {
|
|
178022
|
-
return dir;
|
|
178023
|
-
}
|
|
178024
|
-
dir = parent;
|
|
178025
|
-
}
|
|
178026
|
-
}
|
|
178027
|
-
|
|
178028
|
-
// src/scan/code/worker/scanner-executable.ts
|
|
178029
|
-
var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
|
|
178030
|
-
var SCANNER_EXECUTABLE_NAMES = {
|
|
178031
|
-
swift: "docbridge-swift-scanner",
|
|
178032
|
-
dart: "docbridge_dart_scanner",
|
|
178033
|
-
rust: "docbridge-rust-scanner",
|
|
178034
|
-
go: "docbridge-go-scanner"
|
|
178035
|
-
};
|
|
178036
|
-
function scannerPlatformKey() {
|
|
178037
|
-
return `${process.platform}-${process.arch}`;
|
|
178038
|
-
}
|
|
178039
|
-
function scannerExecutableName(language) {
|
|
178040
|
-
return SCANNER_EXECUTABLE_NAMES[language];
|
|
178041
|
-
}
|
|
178042
|
-
function resolveScannerWorkerCommand(language, options = {}) {
|
|
178043
|
-
const platformKey = options.platformKey ?? scannerPlatformKey();
|
|
178044
|
-
const platformSupported = isSupportedScannerPlatformKey(platformKey);
|
|
178045
|
-
const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
|
|
178046
|
-
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
178047
|
-
if (found !== undefined) {
|
|
178048
|
-
const repair = ensureExecutable(found, options.chmod ?? chmodSync);
|
|
178049
|
-
if (!repair.ok) {
|
|
178050
|
-
return { ok: false, diagnostic: scannerUnavailableDiagnostic(language, repair.reason) };
|
|
178051
|
-
}
|
|
178052
|
-
return { ok: true, command: [found] };
|
|
178053
|
-
}
|
|
178054
|
-
if (!platformSupported) {
|
|
178055
|
-
return {
|
|
178056
|
-
ok: false,
|
|
178057
|
-
diagnostic: scannerUnavailableDiagnostic(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
178058
|
-
};
|
|
178059
|
-
}
|
|
178060
|
-
return {
|
|
178061
|
-
ok: false,
|
|
178062
|
-
diagnostic: scannerUnavailableDiagnostic(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
178063
|
-
};
|
|
178064
|
-
}
|
|
178065
|
-
function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
|
|
178066
|
-
const sourceRoot = options.sourceRoot ?? sourceRootPath();
|
|
178067
|
-
const distRoot = options.distRoot ?? distRootPath();
|
|
178068
|
-
const executable = scannerExecutableName(language);
|
|
178069
|
-
if (language === "swift") {
|
|
178070
|
-
return [
|
|
178071
|
-
join5(sourceRoot, "packages/swift-scanner/.build/release", executable),
|
|
178072
|
-
join5(sourceRoot, "packages/swift-scanner/.build/debug", executable),
|
|
178073
|
-
...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
|
|
178074
|
-
];
|
|
178075
|
-
}
|
|
178076
|
-
if (language === "rust") {
|
|
178077
|
-
return [
|
|
178078
|
-
join5(sourceRoot, "packages/rust-scanner/target/release", executable),
|
|
178079
|
-
join5(sourceRoot, "packages/rust-scanner/target/debug", executable),
|
|
178080
|
-
...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
|
|
178081
|
-
];
|
|
178082
|
-
}
|
|
178083
|
-
if (language === "go") {
|
|
178084
|
-
return [
|
|
178085
|
-
join5(sourceRoot, "packages/go-scanner/bin", executable),
|
|
178086
|
-
...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
|
|
178087
|
-
];
|
|
178088
|
-
}
|
|
178089
|
-
return [
|
|
178090
|
-
join5(sourceRoot, "packages/dart-scanner/bin", executable),
|
|
178091
|
-
...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
|
|
178092
|
-
];
|
|
178093
|
-
}
|
|
178094
|
-
function ensureExecutable(path, chmod) {
|
|
178095
|
-
let mode;
|
|
178096
|
-
try {
|
|
178097
|
-
mode = statSync(path).mode;
|
|
178098
|
-
} catch (error) {
|
|
178099
|
-
return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
|
|
178100
|
-
}
|
|
178101
|
-
if (isExecutableByThisProcess(path)) {
|
|
178102
|
-
return { ok: true };
|
|
178103
|
-
}
|
|
178104
|
-
try {
|
|
178105
|
-
chmod(path, mode | 73);
|
|
178106
|
-
} catch (error) {
|
|
178107
|
-
return {
|
|
178108
|
-
ok: false,
|
|
178109
|
-
reason: `${path} is not executable (mode ${formatMode(mode)}) and the executable ` + `bit could not be restored: ${reasonOf(error)}; run \`chmod +x ${path}\` ` + `or reinstall DocBridge into a writable location`
|
|
178110
|
-
};
|
|
178111
|
-
}
|
|
178112
|
-
return { ok: true };
|
|
178113
|
-
}
|
|
178114
|
-
function isExecutableByThisProcess(path) {
|
|
178115
|
-
try {
|
|
178116
|
-
accessSync(path, constants.X_OK);
|
|
178117
|
-
return true;
|
|
178118
|
-
} catch {
|
|
178119
|
-
return false;
|
|
178120
|
-
}
|
|
178121
|
-
}
|
|
178122
|
-
function formatMode(mode) {
|
|
178123
|
-
return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
|
|
178124
|
-
}
|
|
178125
|
-
function isSupportedScannerPlatformKey(platformKey) {
|
|
178126
|
-
return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
|
|
178127
|
-
}
|
|
178128
|
-
function scannerRootsFromModuleUrl(moduleUrl) {
|
|
178129
|
-
const packageRoot = resolvePackageRoot(moduleUrl);
|
|
178130
|
-
return { distRoot: join5(packageRoot, "dist"), sourceRoot: packageRoot };
|
|
178131
|
-
}
|
|
178132
|
-
function sourceRootPath() {
|
|
178133
|
-
return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
|
|
178134
|
-
}
|
|
178135
|
-
function distRootPath() {
|
|
178136
|
-
return scannerRootsFromModuleUrl(import.meta.url).distRoot;
|
|
178137
|
-
}
|
|
178138
|
-
function scannerUnavailableDiagnostic(language, reason) {
|
|
178139
|
-
const label = language.charAt(0).toUpperCase() + language.slice(1);
|
|
178140
|
-
return {
|
|
178141
|
-
severity: "error",
|
|
178142
|
-
code: "code_scanner_unavailable",
|
|
178143
|
-
language,
|
|
178144
|
-
target: language,
|
|
178145
|
-
message: `${label} scanner worker is unavailable: ${reason}`
|
|
178146
|
-
};
|
|
178147
|
-
}
|
|
178148
|
-
|
|
178149
|
-
// src/scan/code/worker/scanner-worker.ts
|
|
178150
|
-
var import__2020 = __toESM(require_2020(), 1);
|
|
178151
|
-
import { spawnSync } from "node:child_process";
|
|
178152
|
-
import { mkdirSync } from "node:fs";
|
|
178153
|
-
import { tmpdir } from "node:os";
|
|
178154
|
-
import { dirname as dirname2, join as join6 } from "node:path";
|
|
178155
178262
|
// schemas/common-output.schema.json
|
|
178156
178263
|
var common_output_schema_default = {
|
|
178157
178264
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -178199,7 +178306,17 @@ var common_output_schema_default = {
|
|
|
178199
178306
|
target: { type: "string" },
|
|
178200
178307
|
language: {
|
|
178201
178308
|
type: "string",
|
|
178202
|
-
enum: [
|
|
178309
|
+
enum: [
|
|
178310
|
+
"typescript",
|
|
178311
|
+
"swift",
|
|
178312
|
+
"dart",
|
|
178313
|
+
"rust",
|
|
178314
|
+
"go",
|
|
178315
|
+
"javascript",
|
|
178316
|
+
"python",
|
|
178317
|
+
"ruby",
|
|
178318
|
+
"java"
|
|
178319
|
+
]
|
|
178203
178320
|
},
|
|
178204
178321
|
source: { type: "string" },
|
|
178205
178322
|
message: { type: "string" },
|
|
@@ -178219,7 +178336,7 @@ var scanner_worker_schema_default = {
|
|
|
178219
178336
|
$defs: {
|
|
178220
178337
|
language: {
|
|
178221
178338
|
type: "string",
|
|
178222
|
-
enum: ["typescript", "swift", "dart", "rust", "go"]
|
|
178339
|
+
enum: ["typescript", "swift", "dart", "rust", "go", "javascript", "python", "ruby", "java"]
|
|
178223
178340
|
},
|
|
178224
178341
|
codeSymbol: {
|
|
178225
178342
|
type: "object",
|
|
@@ -178335,6 +178452,8 @@ var scanner_worker_schema_default = {
|
|
|
178335
178452
|
};
|
|
178336
178453
|
|
|
178337
178454
|
// src/scan/code/worker/scanner-worker.ts
|
|
178455
|
+
var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
|
|
178456
|
+
var KILLED_WORKER_GRACE_MS = 500;
|
|
178338
178457
|
function createLazyWorkerResponseValidator(compile) {
|
|
178339
178458
|
let validator;
|
|
178340
178459
|
return () => {
|
|
@@ -178352,15 +178471,29 @@ function compileWorkerResponseValidator() {
|
|
|
178352
178471
|
$ref: "#/$defs/response"
|
|
178353
178472
|
});
|
|
178354
178473
|
}
|
|
178355
|
-
function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
|
|
178356
|
-
|
|
178474
|
+
function invokeScannerWorker(request, command, run = runScannerWorkerProcess, stripEnv = []) {
|
|
178475
|
+
return interpretWorkerProcess(request, command, run(processInput(request, command, stripEnv)));
|
|
178476
|
+
}
|
|
178477
|
+
function invokeScannerWorkerAsync(request, command, runAsync = runScannerWorkerProcessAsync, stripEnv = []) {
|
|
178478
|
+
const run = runAsync(processInput(request, command, stripEnv));
|
|
178479
|
+
return {
|
|
178480
|
+
promise: run.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
|
|
178481
|
+
cancel: () => run.cancel()
|
|
178482
|
+
};
|
|
178483
|
+
}
|
|
178484
|
+
function processInput(request, command, stripEnv) {
|
|
178485
|
+
return {
|
|
178357
178486
|
command,
|
|
178358
|
-
stdin: JSON.stringify(request)
|
|
178359
|
-
|
|
178487
|
+
stdin: JSON.stringify(request),
|
|
178488
|
+
stripEnv,
|
|
178489
|
+
timeoutMs: workerTimeoutMs(request.files.length)
|
|
178490
|
+
};
|
|
178491
|
+
}
|
|
178492
|
+
function interpretWorkerProcess(request, command, processResult) {
|
|
178360
178493
|
if (!processResult.ok) {
|
|
178361
178494
|
return {
|
|
178362
178495
|
ok: false,
|
|
178363
|
-
diagnostic:
|
|
178496
|
+
diagnostic: processResult.kind === "execution" ? scannerFailedDiagnostic(request.language, reasonOf(processResult.error)) : scannerUnavailableDiagnostic(request.language, processResult.error, command[0]),
|
|
178364
178497
|
stderr: processResult.stderr
|
|
178365
178498
|
};
|
|
178366
178499
|
}
|
|
@@ -178401,43 +178534,203 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
|
|
|
178401
178534
|
}
|
|
178402
178535
|
function clangModuleCachePath() {
|
|
178403
178536
|
const owner = typeof process.getuid === "function" ? process.getuid() : "shared";
|
|
178404
|
-
return
|
|
178537
|
+
return join4(tmpdir(), `docbridge-clang-module-cache-${owner}`);
|
|
178538
|
+
}
|
|
178539
|
+
function workerProcessEnv(stripEnv = [], platform = process.platform) {
|
|
178540
|
+
const moduleCachePath = clangModuleCachePath();
|
|
178541
|
+
mkdirSync(moduleCachePath, { recursive: true });
|
|
178542
|
+
const fold = platform === "win32" ? (name) => name.toUpperCase() : (name) => name;
|
|
178543
|
+
const stripped = new Set(stripEnv.map(fold));
|
|
178544
|
+
const env = {};
|
|
178545
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
178546
|
+
if (value !== undefined && !stripped.has(fold(name))) {
|
|
178547
|
+
env[name] = value;
|
|
178548
|
+
}
|
|
178549
|
+
}
|
|
178550
|
+
env.CLANG_MODULE_CACHE_PATH = moduleCachePath;
|
|
178551
|
+
return env;
|
|
178552
|
+
}
|
|
178553
|
+
function workerTimeoutMs(fileCount) {
|
|
178554
|
+
return 30000 + 1000 * fileCount;
|
|
178405
178555
|
}
|
|
178406
178556
|
function runScannerWorkerProcess(input) {
|
|
178557
|
+
const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
|
|
178407
178558
|
try {
|
|
178408
|
-
const moduleCachePath = clangModuleCachePath();
|
|
178409
|
-
mkdirSync(moduleCachePath, { recursive: true });
|
|
178410
178559
|
const [executable = "", ...args] = input.command;
|
|
178411
178560
|
const result = spawnSync(executable, args, {
|
|
178412
|
-
env:
|
|
178413
|
-
...process.env,
|
|
178414
|
-
CLANG_MODULE_CACHE_PATH: moduleCachePath
|
|
178415
|
-
},
|
|
178561
|
+
env: workerProcessEnv(input.stripEnv),
|
|
178416
178562
|
input: input.stdin,
|
|
178417
178563
|
encoding: "utf8",
|
|
178418
|
-
maxBuffer:
|
|
178564
|
+
maxBuffer: maxOutputBytes,
|
|
178565
|
+
killSignal: "SIGKILL",
|
|
178566
|
+
...input.timeoutMs === undefined ? {} : { timeout: input.timeoutMs }
|
|
178419
178567
|
});
|
|
178420
|
-
|
|
178421
|
-
|
|
178422
|
-
|
|
178423
|
-
|
|
178424
|
-
|
|
178425
|
-
|
|
178426
|
-
|
|
178427
|
-
|
|
178428
|
-
|
|
178429
|
-
|
|
178568
|
+
return syncWorkerProcessResult(result, input.timeoutMs, maxOutputBytes);
|
|
178569
|
+
} catch (error) {
|
|
178570
|
+
return { ok: false, kind: "start", error, stderr: "" };
|
|
178571
|
+
}
|
|
178572
|
+
}
|
|
178573
|
+
function syncWorkerProcessResult(result, timeoutMs, maxOutputBytes) {
|
|
178574
|
+
const stderr = result.stderr ?? "";
|
|
178575
|
+
if (result.error !== undefined) {
|
|
178576
|
+
return syncSpawnFailure(result.error, workerStarted(result), timeoutMs, maxOutputBytes, stderr);
|
|
178577
|
+
}
|
|
178578
|
+
if (Buffer.byteLength(result.stdout ?? "") + Buffer.byteLength(stderr) > maxOutputBytes) {
|
|
178579
|
+
return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
|
|
178580
|
+
}
|
|
178581
|
+
if (typeof result.status !== "number") {
|
|
178582
|
+
return { ok: false, kind: "execution", error: signalError(result.signal), stderr };
|
|
178583
|
+
}
|
|
178584
|
+
return { ok: true, exitCode: result.status, stdout: result.stdout ?? "", stderr };
|
|
178585
|
+
}
|
|
178586
|
+
function runScannerWorkerProcessAsync(input) {
|
|
178587
|
+
const run = deferred();
|
|
178588
|
+
let settled = false;
|
|
178589
|
+
let timer;
|
|
178590
|
+
let grace;
|
|
178591
|
+
const settle = (complete) => {
|
|
178592
|
+
if (settled) {
|
|
178593
|
+
return;
|
|
178430
178594
|
}
|
|
178431
|
-
|
|
178432
|
-
|
|
178433
|
-
|
|
178434
|
-
|
|
178435
|
-
|
|
178436
|
-
|
|
178595
|
+
settled = true;
|
|
178596
|
+
clearTimeout(timer);
|
|
178597
|
+
clearTimeout(grace);
|
|
178598
|
+
complete();
|
|
178599
|
+
};
|
|
178600
|
+
const ownGroup = process.platform !== "win32";
|
|
178601
|
+
let child;
|
|
178602
|
+
try {
|
|
178603
|
+
const [executable = "", ...args] = input.command;
|
|
178604
|
+
child = spawn(executable, args, { env: workerProcessEnv(input.stripEnv), detached: ownGroup });
|
|
178437
178605
|
} catch (error) {
|
|
178438
|
-
|
|
178606
|
+
settle(() => run.resolve({ ok: false, kind: "start", error, stderr: "" }));
|
|
178607
|
+
return { promise: run.promise, cancel: () => {
|
|
178608
|
+
return;
|
|
178609
|
+
} };
|
|
178610
|
+
}
|
|
178611
|
+
const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
|
|
178612
|
+
const budget = { bytes: 0, limit: maxOutputBytes };
|
|
178613
|
+
const stdout = new OutputCollector(budget);
|
|
178614
|
+
const stderr = new OutputCollector(budget);
|
|
178615
|
+
let failure;
|
|
178616
|
+
const kill = (reason) => {
|
|
178617
|
+
failure ??= reason;
|
|
178618
|
+
killWorker(child, ownGroup);
|
|
178619
|
+
grace ??= setTimeout(() => {
|
|
178620
|
+
closeStreams(child);
|
|
178621
|
+
settle(() => run.resolve(closedProcessResult(null, null, failure, stdout, stderr)));
|
|
178622
|
+
}, KILLED_WORKER_GRACE_MS);
|
|
178623
|
+
};
|
|
178624
|
+
let spawned = false;
|
|
178625
|
+
child.on("spawn", () => {
|
|
178626
|
+
spawned = true;
|
|
178627
|
+
});
|
|
178628
|
+
child.on("error", (error) => {
|
|
178629
|
+
if (!spawned) {
|
|
178630
|
+
settle(() => run.resolve({ ok: false, kind: "start", error, stderr: stderr.text() }));
|
|
178631
|
+
return;
|
|
178632
|
+
}
|
|
178633
|
+
failure ??= error;
|
|
178634
|
+
});
|
|
178635
|
+
child.stdout.on("data", (chunk) => {
|
|
178636
|
+
if (!stdout.add(chunk)) {
|
|
178637
|
+
kill(outputLimitError(maxOutputBytes));
|
|
178638
|
+
}
|
|
178639
|
+
});
|
|
178640
|
+
child.stderr.on("data", (chunk) => {
|
|
178641
|
+
if (!stderr.add(chunk)) {
|
|
178642
|
+
kill(outputLimitError(maxOutputBytes));
|
|
178643
|
+
}
|
|
178644
|
+
});
|
|
178645
|
+
child.stdin.on("error", () => {
|
|
178646
|
+
return;
|
|
178647
|
+
});
|
|
178648
|
+
child.on("close", (code, signal) => {
|
|
178649
|
+
settle(() => run.resolve(closedProcessResult(code, signal, failure, stdout, stderr)));
|
|
178650
|
+
});
|
|
178651
|
+
child.stdin.end(input.stdin);
|
|
178652
|
+
const { timeoutMs } = input;
|
|
178653
|
+
if (timeoutMs !== undefined) {
|
|
178654
|
+
timer = setTimeout(() => kill(timeoutError(timeoutMs)), timeoutMs);
|
|
178655
|
+
}
|
|
178656
|
+
return {
|
|
178657
|
+
promise: run.promise,
|
|
178658
|
+
cancel() {
|
|
178659
|
+
settle(() => {
|
|
178660
|
+
killWorker(child, ownGroup);
|
|
178661
|
+
closeStreams(child);
|
|
178662
|
+
run.reject(abortError());
|
|
178663
|
+
});
|
|
178664
|
+
}
|
|
178665
|
+
};
|
|
178666
|
+
}
|
|
178667
|
+
function killWorker(child, ownGroup) {
|
|
178668
|
+
if (ownGroup && child.pid !== undefined) {
|
|
178669
|
+
try {
|
|
178670
|
+
process.kill(-child.pid, "SIGKILL");
|
|
178671
|
+
return;
|
|
178672
|
+
} catch {}
|
|
178673
|
+
}
|
|
178674
|
+
child.kill("SIGKILL");
|
|
178675
|
+
}
|
|
178676
|
+
function closeStreams(child) {
|
|
178677
|
+
child.stdin.destroy();
|
|
178678
|
+
child.stdout.destroy();
|
|
178679
|
+
child.stderr.destroy();
|
|
178680
|
+
}
|
|
178681
|
+
|
|
178682
|
+
class OutputCollector {
|
|
178683
|
+
budget;
|
|
178684
|
+
chunks = [];
|
|
178685
|
+
constructor(budget) {
|
|
178686
|
+
this.budget = budget;
|
|
178687
|
+
}
|
|
178688
|
+
add(chunk) {
|
|
178689
|
+
this.budget.bytes += chunk.byteLength;
|
|
178690
|
+
if (this.budget.bytes > this.budget.limit) {
|
|
178691
|
+
return false;
|
|
178692
|
+
}
|
|
178693
|
+
this.chunks.push(chunk);
|
|
178694
|
+
return true;
|
|
178695
|
+
}
|
|
178696
|
+
text() {
|
|
178697
|
+
return Buffer.concat(this.chunks).toString("utf8");
|
|
178439
178698
|
}
|
|
178440
178699
|
}
|
|
178700
|
+
function closedProcessResult(code, signal, failure, stdout, stderr) {
|
|
178701
|
+
if (failure !== undefined) {
|
|
178702
|
+
return { ok: false, kind: "execution", error: failure, stderr: stderr.text() };
|
|
178703
|
+
}
|
|
178704
|
+
if (code === null) {
|
|
178705
|
+
return { ok: false, kind: "execution", error: signalError(signal), stderr: stderr.text() };
|
|
178706
|
+
}
|
|
178707
|
+
return { ok: true, exitCode: code, stdout: stdout.text(), stderr: stderr.text() };
|
|
178708
|
+
}
|
|
178709
|
+
function workerStarted(result) {
|
|
178710
|
+
return typeof result.status === "number" || typeof result.signal === "string" || typeof result.pid === "number" && result.pid > 0;
|
|
178711
|
+
}
|
|
178712
|
+
function syncSpawnFailure(error, started, timeoutMs, maxOutputBytes, stderr) {
|
|
178713
|
+
const code = error.code;
|
|
178714
|
+
if (code === "ETIMEDOUT") {
|
|
178715
|
+
return { ok: false, kind: "execution", error: timeoutError(timeoutMs ?? 0), stderr };
|
|
178716
|
+
}
|
|
178717
|
+
if (code === "ENOBUFS") {
|
|
178718
|
+
return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
|
|
178719
|
+
}
|
|
178720
|
+
return { ok: false, kind: started ? "execution" : "start", error, stderr };
|
|
178721
|
+
}
|
|
178722
|
+
function signalError(signal) {
|
|
178723
|
+
const name = signal ?? "unknown";
|
|
178724
|
+
return Object.assign(new Error(`worker terminated by signal ${name}`), { signal: name });
|
|
178725
|
+
}
|
|
178726
|
+
function timeoutError(timeoutMs) {
|
|
178727
|
+
return Object.assign(new Error(`worker timed out after ${timeoutMs} ms`), {
|
|
178728
|
+
code: "ETIMEDOUT"
|
|
178729
|
+
});
|
|
178730
|
+
}
|
|
178731
|
+
function outputLimitError(maxOutputBytes) {
|
|
178732
|
+
return Object.assign(new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`), { code: "ENOBUFS" });
|
|
178733
|
+
}
|
|
178441
178734
|
function validateWorkerResponse(value, request) {
|
|
178442
178735
|
const validator = responseValidator();
|
|
178443
178736
|
if (!validator(value)) {
|
|
@@ -178463,117 +178756,768 @@ function formatSchemaError(errors) {
|
|
|
178463
178756
|
const path = error.instancePath || "/";
|
|
178464
178757
|
return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
|
|
178465
178758
|
}
|
|
178466
|
-
function responseFilesMatchRequest(responseFiles, requestFiles) {
|
|
178467
|
-
if (responseFiles.length !== requestFiles.length) {
|
|
178468
|
-
return false;
|
|
178759
|
+
function responseFilesMatchRequest(responseFiles, requestFiles) {
|
|
178760
|
+
if (responseFiles.length !== requestFiles.length) {
|
|
178761
|
+
return false;
|
|
178762
|
+
}
|
|
178763
|
+
return responseFiles.every((file, index) => {
|
|
178764
|
+
if (!isRecord(file)) {
|
|
178765
|
+
return false;
|
|
178766
|
+
}
|
|
178767
|
+
return file.filePath === requestFiles[index]?.filePath;
|
|
178768
|
+
});
|
|
178769
|
+
}
|
|
178770
|
+
function isRecord(value) {
|
|
178771
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178772
|
+
}
|
|
178773
|
+
function scannerUnavailableDiagnostic(language, error, executable) {
|
|
178774
|
+
const label = languageLabel(language);
|
|
178775
|
+
return {
|
|
178776
|
+
severity: "error",
|
|
178777
|
+
code: "code_scanner_unavailable",
|
|
178778
|
+
language,
|
|
178779
|
+
target: language,
|
|
178780
|
+
message: `${label} scanner worker is unavailable: ${spawnFailureReason(error, executable)}`
|
|
178781
|
+
};
|
|
178782
|
+
}
|
|
178783
|
+
function spawnFailureReason(error, executable) {
|
|
178784
|
+
const reason = reasonOf(error);
|
|
178785
|
+
if (!isExecDenied(error) || executable === undefined) {
|
|
178786
|
+
return reason;
|
|
178787
|
+
}
|
|
178788
|
+
return `${reason}; the scanner is executable but ${dirname(executable)} refuses to ` + `execute it, which a \`noexec\` mount does. Install DocBridge as a project ` + `dependency, or point the installer cache at an exec-capable directory, ` + `instead of running through \`bunx\``;
|
|
178789
|
+
}
|
|
178790
|
+
function isExecDenied(error) {
|
|
178791
|
+
if (!(error instanceof Error)) {
|
|
178792
|
+
return false;
|
|
178793
|
+
}
|
|
178794
|
+
const code = error.code;
|
|
178795
|
+
return code === "EACCES" || code === "EPERM";
|
|
178796
|
+
}
|
|
178797
|
+
function scannerFailedDiagnostic(language, reason) {
|
|
178798
|
+
const label = languageLabel(language);
|
|
178799
|
+
return {
|
|
178800
|
+
severity: "error",
|
|
178801
|
+
code: "code_scanner_failed",
|
|
178802
|
+
language,
|
|
178803
|
+
target: language,
|
|
178804
|
+
message: `${label} scanner worker failed: ${reason}`
|
|
178805
|
+
};
|
|
178806
|
+
}
|
|
178807
|
+
function languageLabel(language) {
|
|
178808
|
+
return language.charAt(0).toUpperCase() + language.slice(1);
|
|
178809
|
+
}
|
|
178810
|
+
|
|
178811
|
+
// src/scan/code/worker/runtime-probe.ts
|
|
178812
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
178813
|
+
var PROBE_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
178814
|
+
function probeRuntime(command, stripEnv, spawn2 = spawnRuntimeProbe) {
|
|
178815
|
+
const [executable = "", ...args] = command;
|
|
178816
|
+
return classifyProbe(spawn2(executable, [...args, "--probe"], {
|
|
178817
|
+
env: workerProcessEnv(stripEnv),
|
|
178818
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
178819
|
+
maxBuffer: PROBE_MAX_OUTPUT_BYTES
|
|
178820
|
+
}));
|
|
178821
|
+
}
|
|
178822
|
+
function probeRuntimeAsync(command, stripEnv, runAsync = runScannerWorkerProcessAsync) {
|
|
178823
|
+
const run = runAsync({
|
|
178824
|
+
command: [...command, "--probe"],
|
|
178825
|
+
stdin: "",
|
|
178826
|
+
stripEnv,
|
|
178827
|
+
timeoutMs: PROBE_TIMEOUT_MS,
|
|
178828
|
+
maxOutputBytes: PROBE_MAX_OUTPUT_BYTES
|
|
178829
|
+
});
|
|
178830
|
+
return {
|
|
178831
|
+
promise: run.promise.then((result) => classifyProbe(spawnResultOf(result))),
|
|
178832
|
+
cancel: () => run.cancel()
|
|
178833
|
+
};
|
|
178834
|
+
}
|
|
178835
|
+
function spawnResultOf(result) {
|
|
178836
|
+
if (result.ok) {
|
|
178837
|
+
return { status: result.exitCode, signal: null, stdout: result.stdout, stderr: result.stderr };
|
|
178838
|
+
}
|
|
178839
|
+
const error = result.error instanceof Error ? result.error : new Error(reasonOf(result.error));
|
|
178840
|
+
const { signal } = error;
|
|
178841
|
+
if (typeof signal === "string") {
|
|
178842
|
+
return { status: null, signal, stdout: "", stderr: result.stderr };
|
|
178843
|
+
}
|
|
178844
|
+
return { status: null, signal: null, stdout: "", stderr: result.stderr, error };
|
|
178845
|
+
}
|
|
178846
|
+
function classifyProbe(result) {
|
|
178847
|
+
if (result.error !== undefined) {
|
|
178848
|
+
return classifySpawnError(result.error);
|
|
178849
|
+
}
|
|
178850
|
+
if (result.status === null) {
|
|
178851
|
+
return { kind: "failed", reason: `probe terminated by signal ${result.signal ?? "unknown"}` };
|
|
178852
|
+
}
|
|
178853
|
+
if (result.status !== 0) {
|
|
178854
|
+
const detail = firstLine(result.stderr);
|
|
178855
|
+
return {
|
|
178856
|
+
kind: "failed",
|
|
178857
|
+
reason: `probe exited with status ${result.status}${detail === "" ? "" : `: ${detail}`}`
|
|
178858
|
+
};
|
|
178859
|
+
}
|
|
178860
|
+
return parseProbeOutput(result.stdout);
|
|
178861
|
+
}
|
|
178862
|
+
function classifySpawnError(error) {
|
|
178863
|
+
const code = error.code;
|
|
178864
|
+
if (code === "ETIMEDOUT") {
|
|
178865
|
+
return { kind: "failed", reason: `probe did not finish within ${PROBE_TIMEOUT_MS / 1000} s` };
|
|
178866
|
+
}
|
|
178867
|
+
if (code === "ENOBUFS") {
|
|
178868
|
+
return {
|
|
178869
|
+
kind: "failed",
|
|
178870
|
+
reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
|
|
178871
|
+
};
|
|
178872
|
+
}
|
|
178873
|
+
return { kind: "unstartable", reason: reasonOf(error) };
|
|
178874
|
+
}
|
|
178875
|
+
function parseProbeOutput(stdout) {
|
|
178876
|
+
const text = stdout.trim();
|
|
178877
|
+
let value;
|
|
178878
|
+
try {
|
|
178879
|
+
value = JSON.parse(text);
|
|
178880
|
+
} catch {
|
|
178881
|
+
return malformed(text);
|
|
178882
|
+
}
|
|
178883
|
+
if (!isRecord2(value)) {
|
|
178884
|
+
return malformed(text);
|
|
178885
|
+
}
|
|
178886
|
+
if (value.ok === true && typeof value.runtime === "string" && typeof value.version === "string") {
|
|
178887
|
+
return { kind: "ok", runtime: value.runtime, version: value.version };
|
|
178888
|
+
}
|
|
178889
|
+
if (value.ok === false && typeof value.reason === "string") {
|
|
178890
|
+
return { kind: "rejected", reason: value.reason };
|
|
178891
|
+
}
|
|
178892
|
+
return malformed(text);
|
|
178893
|
+
}
|
|
178894
|
+
function malformed(text) {
|
|
178895
|
+
const shown = text.length > 200 ? `${text.slice(0, 200)}...` : text;
|
|
178896
|
+
return { kind: "failed", reason: `malformed probe output: ${JSON.stringify(shown)}` };
|
|
178897
|
+
}
|
|
178898
|
+
function firstLine(text) {
|
|
178899
|
+
return text.trim().split(/\r?\n/u)[0] ?? "";
|
|
178900
|
+
}
|
|
178901
|
+
function isRecord2(value) {
|
|
178902
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178903
|
+
}
|
|
178904
|
+
function spawnRuntimeProbe(executable, args, options) {
|
|
178905
|
+
try {
|
|
178906
|
+
const result = spawnSync2(executable, args, {
|
|
178907
|
+
env: options.env,
|
|
178908
|
+
timeout: options.timeout,
|
|
178909
|
+
killSignal: "SIGKILL",
|
|
178910
|
+
maxBuffer: options.maxBuffer,
|
|
178911
|
+
input: "",
|
|
178912
|
+
encoding: "utf8",
|
|
178913
|
+
windowsHide: true
|
|
178914
|
+
});
|
|
178915
|
+
const stdout = result.stdout ?? "";
|
|
178916
|
+
const stderr = result.stderr ?? "";
|
|
178917
|
+
const error = result.error === undefined && Buffer.byteLength(stdout) + Buffer.byteLength(stderr) > options.maxBuffer ? Object.assign(new Error("probe output exceeded maxBuffer"), { code: "ENOBUFS" }) : result.error;
|
|
178918
|
+
return {
|
|
178919
|
+
status: result.status,
|
|
178920
|
+
signal: result.signal,
|
|
178921
|
+
stdout,
|
|
178922
|
+
stderr,
|
|
178923
|
+
...error === undefined ? {} : { error }
|
|
178924
|
+
};
|
|
178925
|
+
} catch (error) {
|
|
178926
|
+
return {
|
|
178927
|
+
status: null,
|
|
178928
|
+
signal: null,
|
|
178929
|
+
stdout: "",
|
|
178930
|
+
stderr: "",
|
|
178931
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
178932
|
+
};
|
|
178933
|
+
}
|
|
178934
|
+
}
|
|
178935
|
+
|
|
178936
|
+
// src/scan/code/worker/scanner-executable.ts
|
|
178937
|
+
import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
|
|
178938
|
+
import { join as join6 } from "node:path";
|
|
178939
|
+
|
|
178940
|
+
// src/shared/package-root.ts
|
|
178941
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
178942
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
178943
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
178944
|
+
function resolvePackageRoot(moduleUrl = import.meta.url) {
|
|
178945
|
+
const modulePath = fileURLToPath2(moduleUrl);
|
|
178946
|
+
let resolved;
|
|
178947
|
+
try {
|
|
178948
|
+
resolved = realpathSync(modulePath);
|
|
178949
|
+
} catch {
|
|
178950
|
+
resolved = modulePath;
|
|
178951
|
+
}
|
|
178952
|
+
let dir = dirname2(resolved);
|
|
178953
|
+
for (;; ) {
|
|
178954
|
+
if (existsSync(join5(dir, "templates", "skills"))) {
|
|
178955
|
+
return dir;
|
|
178956
|
+
}
|
|
178957
|
+
const parent = dirname2(dir);
|
|
178958
|
+
if (parent === dir) {
|
|
178959
|
+
return dir;
|
|
178960
|
+
}
|
|
178961
|
+
dir = parent;
|
|
178962
|
+
}
|
|
178963
|
+
}
|
|
178964
|
+
|
|
178965
|
+
// src/scan/code/worker/scanner-executable.ts
|
|
178966
|
+
var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
|
|
178967
|
+
var SCANNER_EXECUTABLE_NAMES = {
|
|
178968
|
+
swift: "docbridge-swift-scanner",
|
|
178969
|
+
dart: "docbridge_dart_scanner",
|
|
178970
|
+
rust: "docbridge-rust-scanner",
|
|
178971
|
+
go: "docbridge-go-scanner"
|
|
178972
|
+
};
|
|
178973
|
+
function scannerPlatformKey() {
|
|
178974
|
+
return `${process.platform}-${process.arch}`;
|
|
178975
|
+
}
|
|
178976
|
+
function scannerExecutableName(language) {
|
|
178977
|
+
return SCANNER_EXECUTABLE_NAMES[language];
|
|
178978
|
+
}
|
|
178979
|
+
function resolveScannerWorkerCommand(language, options = {}) {
|
|
178980
|
+
const platformKey = options.platformKey ?? scannerPlatformKey();
|
|
178981
|
+
const platformSupported = isSupportedScannerPlatformKey(platformKey);
|
|
178982
|
+
const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
|
|
178983
|
+
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
178984
|
+
if (found !== undefined) {
|
|
178985
|
+
const repair = ensureExecutable(found, options.chmod ?? chmodSync);
|
|
178986
|
+
if (!repair.ok) {
|
|
178987
|
+
return { ok: false, diagnostic: scannerUnavailableDiagnostic2(language, repair.reason) };
|
|
178988
|
+
}
|
|
178989
|
+
return { ok: true, command: [found] };
|
|
178990
|
+
}
|
|
178991
|
+
if (!platformSupported) {
|
|
178992
|
+
return {
|
|
178993
|
+
ok: false,
|
|
178994
|
+
diagnostic: scannerUnavailableDiagnostic2(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
178995
|
+
};
|
|
178996
|
+
}
|
|
178997
|
+
return {
|
|
178998
|
+
ok: false,
|
|
178999
|
+
diagnostic: scannerUnavailableDiagnostic2(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
179000
|
+
};
|
|
179001
|
+
}
|
|
179002
|
+
function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
|
|
179003
|
+
const sourceRoot = options.sourceRoot ?? sourceRootPath();
|
|
179004
|
+
const distRoot = options.distRoot ?? distRootPath();
|
|
179005
|
+
const executable = scannerExecutableName(language);
|
|
179006
|
+
if (language === "swift") {
|
|
179007
|
+
return [
|
|
179008
|
+
join6(sourceRoot, "packages/swift-scanner/.build/release", executable),
|
|
179009
|
+
join6(sourceRoot, "packages/swift-scanner/.build/debug", executable),
|
|
179010
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179011
|
+
];
|
|
179012
|
+
}
|
|
179013
|
+
if (language === "rust") {
|
|
179014
|
+
return [
|
|
179015
|
+
join6(sourceRoot, "packages/rust-scanner/target/release", executable),
|
|
179016
|
+
join6(sourceRoot, "packages/rust-scanner/target/debug", executable),
|
|
179017
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179018
|
+
];
|
|
179019
|
+
}
|
|
179020
|
+
if (language === "go") {
|
|
179021
|
+
return [
|
|
179022
|
+
join6(sourceRoot, "packages/go-scanner/bin", executable),
|
|
179023
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179024
|
+
];
|
|
179025
|
+
}
|
|
179026
|
+
return [
|
|
179027
|
+
join6(sourceRoot, "packages/dart-scanner/bin", executable),
|
|
179028
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179029
|
+
];
|
|
179030
|
+
}
|
|
179031
|
+
function ensureExecutable(path, chmod) {
|
|
179032
|
+
let mode;
|
|
179033
|
+
try {
|
|
179034
|
+
mode = statSync(path).mode;
|
|
179035
|
+
} catch (error) {
|
|
179036
|
+
return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
|
|
179037
|
+
}
|
|
179038
|
+
if (isExecutableByThisProcess(path)) {
|
|
179039
|
+
return { ok: true };
|
|
179040
|
+
}
|
|
179041
|
+
try {
|
|
179042
|
+
chmod(path, mode | 73);
|
|
179043
|
+
} catch (error) {
|
|
179044
|
+
return {
|
|
179045
|
+
ok: false,
|
|
179046
|
+
reason: `${path} is not executable (mode ${formatMode(mode)}) and the executable ` + `bit could not be restored: ${reasonOf(error)}; run \`chmod +x ${path}\` ` + `or reinstall DocBridge into a writable location`
|
|
179047
|
+
};
|
|
179048
|
+
}
|
|
179049
|
+
return { ok: true };
|
|
179050
|
+
}
|
|
179051
|
+
function isExecutableByThisProcess(path) {
|
|
179052
|
+
try {
|
|
179053
|
+
accessSync(path, constants.X_OK);
|
|
179054
|
+
return true;
|
|
179055
|
+
} catch {
|
|
179056
|
+
return false;
|
|
179057
|
+
}
|
|
179058
|
+
}
|
|
179059
|
+
function formatMode(mode) {
|
|
179060
|
+
return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
|
|
179061
|
+
}
|
|
179062
|
+
function isSupportedScannerPlatformKey(platformKey) {
|
|
179063
|
+
return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
|
|
179064
|
+
}
|
|
179065
|
+
function scannerRootsFromModuleUrl(moduleUrl) {
|
|
179066
|
+
const packageRoot = resolvePackageRoot(moduleUrl);
|
|
179067
|
+
return { distRoot: join6(packageRoot, "dist"), sourceRoot: packageRoot };
|
|
179068
|
+
}
|
|
179069
|
+
function sourceRootPath() {
|
|
179070
|
+
return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
|
|
179071
|
+
}
|
|
179072
|
+
function distRootPath() {
|
|
179073
|
+
return scannerRootsFromModuleUrl(import.meta.url).distRoot;
|
|
179074
|
+
}
|
|
179075
|
+
function scannerUnavailableDiagnostic2(language, reason) {
|
|
179076
|
+
const label = language.charAt(0).toUpperCase() + language.slice(1);
|
|
179077
|
+
return {
|
|
179078
|
+
severity: "error",
|
|
179079
|
+
code: "code_scanner_unavailable",
|
|
179080
|
+
language,
|
|
179081
|
+
target: language,
|
|
179082
|
+
message: `${label} scanner worker is unavailable: ${reason}`
|
|
179083
|
+
};
|
|
179084
|
+
}
|
|
179085
|
+
|
|
179086
|
+
// src/scan/code/worker/runtime-worker.ts
|
|
179087
|
+
var RUNTIME_WORKERS = {
|
|
179088
|
+
python: {
|
|
179089
|
+
label: "Python",
|
|
179090
|
+
runtimeName: "CPython",
|
|
179091
|
+
runtimeId: "cpython",
|
|
179092
|
+
floor: [3, 10],
|
|
179093
|
+
floorText: "3.10",
|
|
179094
|
+
candidates: (platform) => platform === "win32" ? [["py", "-3"], ["python"]] : [["python3"], ["python"]],
|
|
179095
|
+
flags: ["-I", "-S"],
|
|
179096
|
+
stripEnv: ["PYTHONPATH", "PYTHONSTARTUP", "PYTHONHOME", "PYTHONSAFEPATH"],
|
|
179097
|
+
entrypoint: {
|
|
179098
|
+
source: "packages/python-scanner/docbridge_python_scanner.py",
|
|
179099
|
+
dist: "workers/python/docbridge_python_scanner.py"
|
|
179100
|
+
}
|
|
179101
|
+
},
|
|
179102
|
+
ruby: {
|
|
179103
|
+
label: "Ruby",
|
|
179104
|
+
runtimeName: "CRuby",
|
|
179105
|
+
runtimeId: "cruby",
|
|
179106
|
+
floor: [3, 3],
|
|
179107
|
+
floorText: "3.3",
|
|
179108
|
+
candidates: () => [["ruby"]],
|
|
179109
|
+
flags: ["--disable=gems,did_you_mean,error_highlight", "-W0"],
|
|
179110
|
+
stripEnv: ["RUBYOPT", "RUBYLIB", "PRISM_FFI_BACKEND"],
|
|
179111
|
+
entrypoint: {
|
|
179112
|
+
source: "packages/ruby-scanner/bin/docbridge-ruby-scanner",
|
|
179113
|
+
dist: "workers/ruby/bin/docbridge-ruby-scanner"
|
|
179114
|
+
}
|
|
179115
|
+
},
|
|
179116
|
+
java: {
|
|
179117
|
+
label: "Java",
|
|
179118
|
+
runtimeName: "JDK",
|
|
179119
|
+
runtimeId: "jdk",
|
|
179120
|
+
floor: [17, 0],
|
|
179121
|
+
floorText: "17",
|
|
179122
|
+
candidates: () => [["java"]],
|
|
179123
|
+
flags: ["-Xshare:auto", "-XX:TieredStopAtLevel=1", "-XX:+UseSerialGC", "-jar"],
|
|
179124
|
+
stripEnv: ["JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS"],
|
|
179125
|
+
entrypoint: {
|
|
179126
|
+
source: "packages/java-scanner/build/docbridge-java-scanner.jar",
|
|
179127
|
+
dist: "workers/java/docbridge-java-scanner.jar"
|
|
179128
|
+
},
|
|
179129
|
+
buildHint: "run `just build-java-scanner` in a source checkout"
|
|
179130
|
+
}
|
|
179131
|
+
};
|
|
179132
|
+
var probeCache = new Map;
|
|
179133
|
+
var probeCacheGeneration = 0;
|
|
179134
|
+
function clearRuntimeProbeCache() {
|
|
179135
|
+
probeCache.clear();
|
|
179136
|
+
probeCacheGeneration += 1;
|
|
179137
|
+
}
|
|
179138
|
+
function rememberProbe(key, generation, outcome) {
|
|
179139
|
+
if (generation === probeCacheGeneration) {
|
|
179140
|
+
probeCache.set(key, outcome);
|
|
179141
|
+
}
|
|
179142
|
+
}
|
|
179143
|
+
function resolveRuntimeWorkerCommand(language, options) {
|
|
179144
|
+
const env = options.env ?? process.env;
|
|
179145
|
+
const probe = options.probe ?? probeRuntime;
|
|
179146
|
+
const steps = resolutionSteps(language, options, env);
|
|
179147
|
+
let step = steps.next();
|
|
179148
|
+
while (step.done !== true) {
|
|
179149
|
+
step = steps.next(cachedProbe(step.value, env, probe));
|
|
179150
|
+
}
|
|
179151
|
+
return step.value;
|
|
179152
|
+
}
|
|
179153
|
+
function resolveRuntimeWorkerCommandAsync(language, options) {
|
|
179154
|
+
const env = options.env ?? process.env;
|
|
179155
|
+
const probeAsync = options.probeAsync ?? probeRuntimeAsync;
|
|
179156
|
+
return cancelableSequence(async (run) => {
|
|
179157
|
+
const steps = resolutionSteps(language, options, env);
|
|
179158
|
+
let step = steps.next();
|
|
179159
|
+
while (step.done !== true) {
|
|
179160
|
+
const request = step.value;
|
|
179161
|
+
step = steps.next(await run(() => cachedProbeAsync(request, env, probeAsync)));
|
|
179162
|
+
}
|
|
179163
|
+
return step.value;
|
|
179164
|
+
});
|
|
179165
|
+
}
|
|
179166
|
+
function* resolutionSteps(language, options, env) {
|
|
179167
|
+
const spec = RUNTIME_WORKERS[language];
|
|
179168
|
+
const entrypoint = findEntrypoint(spec, options);
|
|
179169
|
+
if (!entrypoint.ok) {
|
|
179170
|
+
return failure(spec, language, "code_scanner_unavailable", entrypoint.reason);
|
|
179171
|
+
}
|
|
179172
|
+
const platform = options.platform ?? process.platform;
|
|
179173
|
+
const commandFor = (runtime) => [...runtime, ...spec.flags, entrypoint.path];
|
|
179174
|
+
const usable = (command, runtime, verdict) => ({
|
|
179175
|
+
ok: true,
|
|
179176
|
+
command,
|
|
179177
|
+
stripEnv: spec.stripEnv,
|
|
179178
|
+
runtime: [verdict.runtime, verdict.version, locateExecutable(runtime[0] ?? "", env, platform)]
|
|
179179
|
+
});
|
|
179180
|
+
const override = explicitOverride(language, options, env);
|
|
179181
|
+
if (override !== undefined) {
|
|
179182
|
+
const runtime = override.argv.map((part, index) => index === 0 ? resolveExecutable(part, options.projectRoot) : part);
|
|
179183
|
+
const command = commandFor(runtime);
|
|
179184
|
+
const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
|
|
179185
|
+
if (verdict.ok) {
|
|
179186
|
+
return usable(command, runtime, verdict);
|
|
179187
|
+
}
|
|
179188
|
+
return failure(spec, language, verdict.code, `${override.source} (${runtime.join(" ")}) ${verdict.reason}; no other runtime is tried ` + `while ${override.source} is set`);
|
|
179189
|
+
}
|
|
179190
|
+
const rejections = [];
|
|
179191
|
+
for (const runtime of spec.candidates(platform)) {
|
|
179192
|
+
const command = commandFor(runtime);
|
|
179193
|
+
const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
|
|
179194
|
+
if (verdict.ok) {
|
|
179195
|
+
return usable(command, runtime, verdict);
|
|
179196
|
+
}
|
|
179197
|
+
rejections.push({ runtime, verdict });
|
|
179198
|
+
}
|
|
179199
|
+
const outcomes = rejections.map(({ runtime, verdict }) => `${runtime.join(" ")} ${verdict.reason}`).join("; ");
|
|
179200
|
+
const code = rejections.find(({ verdict }) => verdict.startable)?.verdict.code ?? "code_scanner_unavailable";
|
|
179201
|
+
return failure(spec, language, code, `no usable ${spec.runtimeName} ${spec.floorText} or later found: ${outcomes}. Install ` + `${spec.runtimeName} ${spec.floorText} or later, or set scanners.${language}.command or ` + `${environmentVariable(language)}`);
|
|
179202
|
+
}
|
|
179203
|
+
function findEntrypoint(spec, options) {
|
|
179204
|
+
const roots = scannerRootsFromModuleUrl(import.meta.url);
|
|
179205
|
+
const source = join7(options.sourceRoot ?? roots.sourceRoot, spec.entrypoint.source);
|
|
179206
|
+
const dist = join7(options.distRoot ?? roots.distRoot, spec.entrypoint.dist);
|
|
179207
|
+
const found = [source, dist].find((path) => existsSync3(path));
|
|
179208
|
+
if (found !== undefined) {
|
|
179209
|
+
return { ok: true, path: found };
|
|
179210
|
+
}
|
|
179211
|
+
const hint = spec.buildHint === undefined ? "" : `; ${spec.buildHint}`;
|
|
179212
|
+
return {
|
|
179213
|
+
ok: false,
|
|
179214
|
+
reason: `the bundled worker is missing; looked for ${source} and ${dist}${hint}`
|
|
179215
|
+
};
|
|
179216
|
+
}
|
|
179217
|
+
function explicitOverride(language, options, env) {
|
|
179218
|
+
if (options.command !== undefined && options.command.length > 0) {
|
|
179219
|
+
return { source: `scanners.${language}.command`, argv: options.command };
|
|
179220
|
+
}
|
|
179221
|
+
const variable = environmentVariable(language);
|
|
179222
|
+
const value = env[variable];
|
|
179223
|
+
if (value !== undefined && value !== "") {
|
|
179224
|
+
return { source: variable, argv: [value] };
|
|
179225
|
+
}
|
|
179226
|
+
return;
|
|
179227
|
+
}
|
|
179228
|
+
function environmentVariable(language) {
|
|
179229
|
+
return `DOCBRIDGE_${language.toUpperCase()}_RUNTIME`;
|
|
179230
|
+
}
|
|
179231
|
+
function resolveExecutable(executable, projectRoot) {
|
|
179232
|
+
if (isAbsolute2(executable) || !/[/\\]/u.test(executable)) {
|
|
179233
|
+
return executable;
|
|
179234
|
+
}
|
|
179235
|
+
return resolve2(projectRoot, executable);
|
|
179236
|
+
}
|
|
179237
|
+
function locateExecutable(executable, env, platform) {
|
|
179238
|
+
const candidates = /[/\\]/u.test(executable) ? [executable] : pathCandidates(executable, env, platform);
|
|
179239
|
+
const found = candidates.find((candidate) => isExecutableFile(candidate, platform));
|
|
179240
|
+
if (found === undefined) {
|
|
179241
|
+
return executable;
|
|
178469
179242
|
}
|
|
178470
|
-
|
|
178471
|
-
|
|
179243
|
+
try {
|
|
179244
|
+
return realpathSync2(found);
|
|
179245
|
+
} catch {
|
|
179246
|
+
return found;
|
|
179247
|
+
}
|
|
179248
|
+
}
|
|
179249
|
+
function pathCandidates(name, env, platform) {
|
|
179250
|
+
const windows = platform === "win32";
|
|
179251
|
+
const directories = (env.PATH ?? "").split(windows ? ";" : ":").filter((dir) => dir !== "");
|
|
179252
|
+
const extensions = windows && extname(name) === "" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
|
|
179253
|
+
return directories.flatMap((directory) => extensions.map((extension) => join7(directory, `${name}${extension}`)));
|
|
179254
|
+
}
|
|
179255
|
+
function isExecutableFile(path, platform) {
|
|
179256
|
+
try {
|
|
179257
|
+
if (!statSync2(path).isFile()) {
|
|
178472
179258
|
return false;
|
|
178473
179259
|
}
|
|
178474
|
-
|
|
178475
|
-
|
|
179260
|
+
if (platform !== "win32") {
|
|
179261
|
+
accessSync2(path, constants2.X_OK);
|
|
179262
|
+
}
|
|
179263
|
+
return true;
|
|
179264
|
+
} catch {
|
|
179265
|
+
return false;
|
|
179266
|
+
}
|
|
178476
179267
|
}
|
|
178477
|
-
function
|
|
178478
|
-
|
|
179268
|
+
function cachedProbe({ command, stripEnv }, env, probe) {
|
|
179269
|
+
const key = probeCacheKey(command, env);
|
|
179270
|
+
const cached = probeCache.get(key);
|
|
179271
|
+
if (cached !== undefined) {
|
|
179272
|
+
return cached;
|
|
179273
|
+
}
|
|
179274
|
+
const generation = probeCacheGeneration;
|
|
179275
|
+
const outcome = probe(command, stripEnv);
|
|
179276
|
+
rememberProbe(key, generation, outcome);
|
|
179277
|
+
return outcome;
|
|
178479
179278
|
}
|
|
178480
|
-
function
|
|
178481
|
-
const
|
|
178482
|
-
|
|
178483
|
-
|
|
178484
|
-
|
|
178485
|
-
|
|
178486
|
-
|
|
178487
|
-
|
|
178488
|
-
|
|
179279
|
+
function cachedProbeAsync({ command, stripEnv }, env, probeAsync) {
|
|
179280
|
+
const key = probeCacheKey(command, env);
|
|
179281
|
+
const cached = probeCache.get(key);
|
|
179282
|
+
if (cached !== undefined) {
|
|
179283
|
+
return settledCancelable(cached);
|
|
179284
|
+
}
|
|
179285
|
+
const generation = probeCacheGeneration;
|
|
179286
|
+
return mapCancelable(probeAsync(command, stripEnv), (outcome) => {
|
|
179287
|
+
rememberProbe(key, generation, outcome);
|
|
179288
|
+
return outcome;
|
|
179289
|
+
});
|
|
178489
179290
|
}
|
|
178490
|
-
function
|
|
178491
|
-
const
|
|
178492
|
-
|
|
178493
|
-
|
|
179291
|
+
function probeCacheKey(command, env) {
|
|
179292
|
+
const docbridgeVariables = Object.entries(env).filter(([name]) => name.startsWith("DOCBRIDGE_")).toSorted(([left], [right]) => left.localeCompare(right));
|
|
179293
|
+
return JSON.stringify([command, env.PATH ?? "", docbridgeVariables]);
|
|
179294
|
+
}
|
|
179295
|
+
function judge(spec, outcome) {
|
|
179296
|
+
switch (outcome.kind) {
|
|
179297
|
+
case "unstartable":
|
|
179298
|
+
return rejected("code_scanner_unavailable", false, `could not be started: ${outcome.reason}`);
|
|
179299
|
+
case "failed":
|
|
179300
|
+
return rejected("code_scanner_failed", true, `failed its probe: ${outcome.reason}`);
|
|
179301
|
+
case "rejected":
|
|
179302
|
+
return rejected("code_scanner_unavailable", true, `is not a usable ${spec.runtimeName} ${spec.floorText} or later: ${outcome.reason}`);
|
|
179303
|
+
case "ok":
|
|
179304
|
+
return judgeVersion(spec, outcome.runtime, outcome.version);
|
|
178494
179305
|
}
|
|
178495
|
-
return `${reason}; the scanner is executable but ${dirname2(executable)} refuses to ` + `execute it, which a \`noexec\` mount does. Install DocBridge as a project ` + `dependency, or point the installer cache at an exec-capable directory, ` + `instead of running through \`bunx\``;
|
|
178496
179306
|
}
|
|
178497
|
-
function
|
|
178498
|
-
if (
|
|
178499
|
-
return
|
|
179307
|
+
function judgeVersion(spec, runtime, version) {
|
|
179308
|
+
if (runtime !== spec.runtimeId) {
|
|
179309
|
+
return rejected("code_scanner_unavailable", true, `reports runtime ${runtime} ${version}, not ${spec.runtimeName}`);
|
|
178500
179310
|
}
|
|
178501
|
-
const
|
|
178502
|
-
|
|
179311
|
+
const match = /^(\d+)(?:\.(\d+))?/u.exec(version);
|
|
179312
|
+
if (match === null) {
|
|
179313
|
+
return rejected("code_scanner_failed", true, `reported an unreadable version: ${version}`);
|
|
179314
|
+
}
|
|
179315
|
+
const major = Number(match[1]);
|
|
179316
|
+
const minor = Number(match[2] ?? "0");
|
|
179317
|
+
const [floorMajor, floorMinor] = spec.floor;
|
|
179318
|
+
if (major < floorMajor || major === floorMajor && minor < floorMinor) {
|
|
179319
|
+
return rejected("code_scanner_unavailable", true, `is ${spec.runtimeName} ${version}, below the ${spec.floorText} floor`);
|
|
179320
|
+
}
|
|
179321
|
+
return { ok: true, runtime, version };
|
|
178503
179322
|
}
|
|
178504
|
-
function
|
|
178505
|
-
|
|
179323
|
+
function rejected(code, startable, reason) {
|
|
179324
|
+
return { ok: false, code, startable, reason };
|
|
179325
|
+
}
|
|
179326
|
+
function failure(spec, language, code, reason) {
|
|
179327
|
+
const state = code === "code_scanner_unavailable" ? "is unavailable" : "failed";
|
|
178506
179328
|
return {
|
|
178507
|
-
|
|
178508
|
-
|
|
178509
|
-
|
|
178510
|
-
|
|
178511
|
-
|
|
179329
|
+
ok: false,
|
|
179330
|
+
diagnostic: {
|
|
179331
|
+
severity: "error",
|
|
179332
|
+
code,
|
|
179333
|
+
language,
|
|
179334
|
+
target: language,
|
|
179335
|
+
message: `${spec.label} scanner worker ${state}: ${reason}`
|
|
179336
|
+
}
|
|
178512
179337
|
};
|
|
178513
179338
|
}
|
|
178514
|
-
function languageLabel(language) {
|
|
178515
|
-
return language.charAt(0).toUpperCase() + language.slice(1);
|
|
178516
|
-
}
|
|
178517
179339
|
|
|
178518
179340
|
// src/scan/code/dispatch.ts
|
|
178519
179341
|
function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
|
|
179342
|
+
const { commandAsync } = adapterOptions;
|
|
179343
|
+
const bound = (context) => boundWorkerAdapter(language, workerCommand(command(context)), adapterOptions).adapter;
|
|
179344
|
+
const prepare = (context) => mapCancelable(commandAsync?.(context) ?? settledCancelable(command(context)), (value) => boundWorkerAdapter(language, workerCommand(value), adapterOptions));
|
|
179345
|
+
const scanBatchAsync = (files, options, context) => {
|
|
179346
|
+
if (files.length === 0) {
|
|
179347
|
+
return settledCancelable([]);
|
|
179348
|
+
}
|
|
179349
|
+
if (commandAsync === undefined) {
|
|
179350
|
+
return bound(context).scanFilesAsync(files, options, context);
|
|
179351
|
+
}
|
|
179352
|
+
return cancelableSequence(async (step) => {
|
|
179353
|
+
const { adapter } = await step(() => prepare(context));
|
|
179354
|
+
return step(() => adapter.scanFilesAsync(files, options, context));
|
|
179355
|
+
});
|
|
179356
|
+
};
|
|
178520
179357
|
return {
|
|
179358
|
+
language,
|
|
179359
|
+
scanFile: (filePath, content, options, context) => bound(context).scanFile(filePath, content, options, context),
|
|
179360
|
+
scanFiles: (files, options, context) => files.length === 0 ? [] : bound(context).scanFiles(files, options, context),
|
|
179361
|
+
scanFilesAsync: scanBatchAsync,
|
|
179362
|
+
prepare
|
|
179363
|
+
};
|
|
179364
|
+
}
|
|
179365
|
+
function boundWorkerAdapter(language, resolved, adapterOptions) {
|
|
179366
|
+
const argv = resolved.ok ? resolved.command : [];
|
|
179367
|
+
const runtime = resolved.ok ? resolved.runtime : [];
|
|
179368
|
+
const adapter = {
|
|
178521
179369
|
language,
|
|
178522
179370
|
scanFile(filePath, content, options, context) {
|
|
178523
|
-
const
|
|
178524
|
-
|
|
178525
|
-
|
|
178526
|
-
|
|
178527
|
-
|
|
178528
|
-
|
|
179371
|
+
const [scan] = adapter.scanFiles([{ filePath, content }], options, context);
|
|
179372
|
+
return scan ?? emptyScan(language, filePath);
|
|
179373
|
+
},
|
|
179374
|
+
scanFiles(files, options, context) {
|
|
179375
|
+
if (files.length === 0) {
|
|
179376
|
+
return [];
|
|
178529
179377
|
}
|
|
178530
|
-
|
|
178531
|
-
|
|
178532
|
-
requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
|
|
178533
|
-
language,
|
|
178534
|
-
projectRoot: context.projectRoot,
|
|
178535
|
-
files: [{ filePath, content }],
|
|
178536
|
-
options
|
|
178537
|
-
}, commandResolution.command, adapterOptions.run);
|
|
178538
|
-
if (result.ok) {
|
|
178539
|
-
const scan = result.codeFiles[0];
|
|
178540
|
-
return scan ?? emptyScan(language, filePath);
|
|
179378
|
+
if (!resolved.ok) {
|
|
179379
|
+
return failedBatch(language, files, resolved.diagnostic);
|
|
178541
179380
|
}
|
|
178542
|
-
|
|
178543
|
-
|
|
178544
|
-
|
|
178545
|
-
|
|
178546
|
-
|
|
179381
|
+
const result = invokeScannerWorker(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.run, resolved.stripEnv);
|
|
179382
|
+
return result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic);
|
|
179383
|
+
},
|
|
179384
|
+
scanFilesAsync(files, options, context) {
|
|
179385
|
+
if (files.length === 0) {
|
|
179386
|
+
return settledCancelable([]);
|
|
179387
|
+
}
|
|
179388
|
+
if (!resolved.ok) {
|
|
179389
|
+
return settledCancelable(failedBatch(language, files, resolved.diagnostic));
|
|
179390
|
+
}
|
|
179391
|
+
const task = invokeScannerWorkerAsync(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.runAsync, resolved.stripEnv);
|
|
179392
|
+
return mapCancelable(task, (result) => result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic));
|
|
179393
|
+
},
|
|
179394
|
+
prepare: () => settledCancelable({ argv, runtime, adapter })
|
|
178547
179395
|
};
|
|
179396
|
+
return { argv, runtime, adapter };
|
|
178548
179397
|
}
|
|
178549
179398
|
function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
|
|
178550
|
-
const
|
|
178551
|
-
const
|
|
179399
|
+
const plan = planCodeScan(files, codeInclude, read, options.onContent);
|
|
179400
|
+
const context = scanContext(projectRoot, options);
|
|
179401
|
+
for (const batch of plan.batches) {
|
|
179402
|
+
const adapter = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
|
|
179403
|
+
fillBatch(plan, batch, adapter.scanFiles(batch.files, batch.options, context));
|
|
179404
|
+
}
|
|
179405
|
+
return assemblePlan(plan);
|
|
179406
|
+
}
|
|
179407
|
+
function scanContext(projectRoot, options) {
|
|
179408
|
+
return options.scanners === undefined ? { projectRoot } : { projectRoot, scanners: options.scanners };
|
|
179409
|
+
}
|
|
179410
|
+
function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {}) {
|
|
179411
|
+
const plan = planCodeScan(files, codeInclude, read, options.onContent);
|
|
179412
|
+
const context = scanContext(projectRoot, options);
|
|
179413
|
+
return cancelableSequence(async (step) => {
|
|
179414
|
+
const cache = new Map;
|
|
179415
|
+
for (const batch of plan.batches) {
|
|
179416
|
+
const base = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
|
|
179417
|
+
const prepare = base.prepare?.bind(base);
|
|
179418
|
+
const { argv, runtime, adapter } = prepare === undefined ? { argv: [], adapter: base } : await step(() => prepare(context));
|
|
179419
|
+
const keys = batch.files.map((file) => codeScanCacheKey(batch.language, file.filePath, file.content, batch.options, argv, runtime));
|
|
179420
|
+
const cached = keys.map((key) => options.cache?.get(key));
|
|
179421
|
+
const missing = batch.files.filter((_, index) => cached[index] === undefined);
|
|
179422
|
+
const scanned = missing.length === 0 ? [] : await step(() => scanFilesAsync(adapter, missing, batch.options, context));
|
|
179423
|
+
let next = 0;
|
|
179424
|
+
const results = cached.map((hit) => hit ?? scanned[next++]);
|
|
179425
|
+
fillBatch(plan, batch, results);
|
|
179426
|
+
results.forEach((scan, index) => {
|
|
179427
|
+
const key = keys[index];
|
|
179428
|
+
if (scan !== undefined && key !== undefined && isReusableScan(scan)) {
|
|
179429
|
+
cache.set(key, scan);
|
|
179430
|
+
}
|
|
179431
|
+
});
|
|
179432
|
+
}
|
|
179433
|
+
return { ...assemblePlan(plan), cache };
|
|
179434
|
+
});
|
|
179435
|
+
}
|
|
179436
|
+
function scanFilesAsync(adapter, files, options, context) {
|
|
179437
|
+
return adapter.scanFilesAsync?.(files, options, context) ?? settledCancelable(adapter.scanFiles(files, options, context));
|
|
179438
|
+
}
|
|
179439
|
+
var builtInAdapters = {
|
|
179440
|
+
typescript: typeScriptAdapter,
|
|
179441
|
+
swift: createScannerWorkerAdapter("swift", () => resolveScannerWorkerCommand("swift")),
|
|
179442
|
+
dart: createScannerWorkerAdapter("dart", () => resolveScannerWorkerCommand("dart")),
|
|
179443
|
+
rust: createScannerWorkerAdapter("rust", () => resolveScannerWorkerCommand("rust")),
|
|
179444
|
+
go: createScannerWorkerAdapter("go", () => resolveScannerWorkerCommand("go")),
|
|
179445
|
+
javascript: javaScriptAdapter,
|
|
179446
|
+
python: runtimeWorkerAdapter("python"),
|
|
179447
|
+
ruby: runtimeWorkerAdapter("ruby"),
|
|
179448
|
+
java: runtimeWorkerAdapter("java")
|
|
179449
|
+
};
|
|
179450
|
+
function runtimeWorkerAdapter(language) {
|
|
179451
|
+
const resolution = ({ projectRoot, scanners }) => {
|
|
179452
|
+
const command = scanners?.[language]?.command;
|
|
179453
|
+
return command === undefined ? { projectRoot } : { projectRoot, command };
|
|
179454
|
+
};
|
|
179455
|
+
return createScannerWorkerAdapter(language, (context) => resolveRuntimeWorkerCommand(language, resolution(context)), { commandAsync: (context) => resolveRuntimeWorkerCommandAsync(language, resolution(context)) });
|
|
179456
|
+
}
|
|
179457
|
+
function planCodeScan(files, codeInclude, read, onContent) {
|
|
179458
|
+
const slots = [];
|
|
179459
|
+
const batches = new Map;
|
|
178552
179460
|
for (const { language, relPath } of files) {
|
|
178553
179461
|
const result = read(relPath);
|
|
178554
179462
|
if (!result.ok) {
|
|
178555
|
-
|
|
179463
|
+
slots.push({ read: false, diagnostic: result.diagnostic });
|
|
178556
179464
|
continue;
|
|
178557
179465
|
}
|
|
178558
|
-
|
|
178559
|
-
|
|
178560
|
-
|
|
178561
|
-
|
|
178562
|
-
|
|
178563
|
-
|
|
178564
|
-
});
|
|
179466
|
+
onContent?.(relPath, result.content);
|
|
179467
|
+
let batch = batches.get(language);
|
|
179468
|
+
if (batch === undefined) {
|
|
179469
|
+
batch = { language, options: scanOptionsFor(codeInclude, language), files: [], slots: [] };
|
|
179470
|
+
batches.set(language, batch);
|
|
179471
|
+
}
|
|
179472
|
+
batch.files.push({ filePath: relPath, content: result.content });
|
|
179473
|
+
batch.slots.push(slots.length);
|
|
179474
|
+
slots.push({ read: true, language, relPath });
|
|
179475
|
+
}
|
|
179476
|
+
return { slots, batches: [...batches.values()] };
|
|
179477
|
+
}
|
|
179478
|
+
function fillBatch(plan, batch, results) {
|
|
179479
|
+
batch.slots.forEach((slotIndex, resultIndex) => {
|
|
179480
|
+
const slot = plan.slots[slotIndex];
|
|
179481
|
+
const scan = results[resultIndex];
|
|
179482
|
+
if (slot?.read === true && scan !== undefined) {
|
|
179483
|
+
slot.scan = scan;
|
|
179484
|
+
}
|
|
179485
|
+
});
|
|
179486
|
+
}
|
|
179487
|
+
function assemblePlan(plan) {
|
|
179488
|
+
const codeFiles = [];
|
|
179489
|
+
const diagnostics = [];
|
|
179490
|
+
for (const slot of plan.slots) {
|
|
179491
|
+
if (!slot.read) {
|
|
179492
|
+
diagnostics.push(slot.diagnostic);
|
|
179493
|
+
continue;
|
|
179494
|
+
}
|
|
179495
|
+
const scan = slot.scan ?? emptyScan(slot.language, slot.relPath);
|
|
178565
179496
|
diagnostics.push(...scan.diagnostics);
|
|
178566
179497
|
codeFiles.push(scan);
|
|
178567
179498
|
}
|
|
178568
179499
|
return { codeFiles, diagnostics };
|
|
178569
179500
|
}
|
|
178570
|
-
|
|
178571
|
-
|
|
178572
|
-
|
|
178573
|
-
|
|
178574
|
-
|
|
178575
|
-
|
|
178576
|
-
|
|
179501
|
+
function scanOptionsFor(codeInclude, language) {
|
|
179502
|
+
const visibility = codeInclude[language]?.visibility;
|
|
179503
|
+
return visibility !== undefined ? { visibility } : {};
|
|
179504
|
+
}
|
|
179505
|
+
function workerRequest(language, files, options, context, adapterOptions) {
|
|
179506
|
+
return {
|
|
179507
|
+
schemaVersion: 1,
|
|
179508
|
+
requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
|
|
179509
|
+
language,
|
|
179510
|
+
projectRoot: context.projectRoot,
|
|
179511
|
+
files: files.map(({ filePath, content }) => ({ filePath, content })),
|
|
179512
|
+
options
|
|
179513
|
+
};
|
|
179514
|
+
}
|
|
179515
|
+
function failedBatch(language, files, diagnostic) {
|
|
179516
|
+
return files.map(({ filePath }) => ({
|
|
179517
|
+
...emptyScan(language, filePath),
|
|
179518
|
+
diagnostics: [{ ...diagnostic, target: filePath }]
|
|
179519
|
+
}));
|
|
179520
|
+
}
|
|
178577
179521
|
function emptyScan(language, filePath) {
|
|
178578
179522
|
return {
|
|
178579
179523
|
language,
|
|
@@ -178584,11 +179528,16 @@ function emptyScan(language, filePath) {
|
|
|
178584
179528
|
diagnostics: []
|
|
178585
179529
|
};
|
|
178586
179530
|
}
|
|
178587
|
-
function
|
|
178588
|
-
|
|
178589
|
-
}
|
|
178590
|
-
|
|
178591
|
-
return
|
|
179531
|
+
function workerCommand(value) {
|
|
179532
|
+
if (Array.isArray(value)) {
|
|
179533
|
+
return { ok: true, command: value, stripEnv: [], runtime: [] };
|
|
179534
|
+
}
|
|
179535
|
+
return value.ok ? {
|
|
179536
|
+
ok: true,
|
|
179537
|
+
command: value.command,
|
|
179538
|
+
stripEnv: value.stripEnv ?? [],
|
|
179539
|
+
runtime: value.runtime ?? []
|
|
179540
|
+
} : value;
|
|
178592
179541
|
}
|
|
178593
179542
|
|
|
178594
179543
|
// src/scan/markdown/markdown.ts
|
|
@@ -178823,6 +179772,42 @@ function flushDanglingWith(pending, diagnostics, message) {
|
|
|
178823
179772
|
|
|
178824
179773
|
// src/query/project-scan.ts
|
|
178825
179774
|
function scanProject(options) {
|
|
179775
|
+
const loaded = loadProjectScan(options, options.keepContent === true);
|
|
179776
|
+
if (!loaded.ok) {
|
|
179777
|
+
return loaded;
|
|
179778
|
+
}
|
|
179779
|
+
const { inputs } = loaded;
|
|
179780
|
+
const codeScan = scanCodeFiles(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, codeScanOptions(inputs, options));
|
|
179781
|
+
const docReads = readDocFiles(inputs, options);
|
|
179782
|
+
return { ok: true, scan: finishProjectScan(inputs, codeScan, docReads, options.buildGraph) };
|
|
179783
|
+
}
|
|
179784
|
+
function scanProjectAsync(options) {
|
|
179785
|
+
const loaded = loadProjectScan(options, true);
|
|
179786
|
+
if (!loaded.ok) {
|
|
179787
|
+
return cancelableSequence(async () => loaded);
|
|
179788
|
+
}
|
|
179789
|
+
const { inputs } = loaded;
|
|
179790
|
+
const fingerprint = JSON.stringify(inputs.config);
|
|
179791
|
+
const sameConfiguration = options.cache.fingerprint === fingerprint;
|
|
179792
|
+
if (!sameConfiguration && options.cache.fingerprint !== "") {
|
|
179793
|
+
options.onConfigurationChange?.();
|
|
179794
|
+
}
|
|
179795
|
+
const codeScan = scanCodeFilesAsync(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
|
|
179796
|
+
...codeScanOptions(inputs, options),
|
|
179797
|
+
...sameConfiguration ? { cache: options.cache.entries } : {}
|
|
179798
|
+
});
|
|
179799
|
+
const docReads = readDocFiles(inputs, options);
|
|
179800
|
+
return cancelableSequence(async (step) => {
|
|
179801
|
+
const { cache, ...code } = await step(codeScan);
|
|
179802
|
+
const scan = finishProjectScan(inputs, code, docReads, true);
|
|
179803
|
+
return {
|
|
179804
|
+
ok: true,
|
|
179805
|
+
scan,
|
|
179806
|
+
cache: { fingerprint, entries: cache }
|
|
179807
|
+
};
|
|
179808
|
+
});
|
|
179809
|
+
}
|
|
179810
|
+
function loadProjectScan(options, keepContent) {
|
|
178826
179811
|
const configResult = loadConfig(options.projectRoot);
|
|
178827
179812
|
if (!configResult.ok) {
|
|
178828
179813
|
return { ok: false, diagnostics: configResult.diagnostics };
|
|
@@ -178832,32 +179817,51 @@ function scanProject(options) {
|
|
|
178832
179817
|
return { ok: false, diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics] };
|
|
178833
179818
|
}
|
|
178834
179819
|
const collectCode = options.collectCode ?? collectCodeFiles;
|
|
178835
|
-
|
|
178836
|
-
|
|
178837
|
-
|
|
178838
|
-
|
|
178839
|
-
|
|
178840
|
-
|
|
178841
|
-
|
|
178842
|
-
|
|
179820
|
+
return {
|
|
179821
|
+
ok: true,
|
|
179822
|
+
inputs: {
|
|
179823
|
+
config: configResult.config,
|
|
179824
|
+
manifest: manifestResult.manifest,
|
|
179825
|
+
diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics],
|
|
179826
|
+
codeFiles: collectCode(options.projectRoot, configResult.config.include.code),
|
|
179827
|
+
readFile: options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath)),
|
|
179828
|
+
contentByFile: keepContent ? new Map : undefined
|
|
179829
|
+
}
|
|
179830
|
+
};
|
|
179831
|
+
}
|
|
179832
|
+
function codeScanOptions(inputs, options) {
|
|
179833
|
+
const { contentByFile } = inputs;
|
|
179834
|
+
const { scanners } = inputs.config;
|
|
179835
|
+
return {
|
|
178843
179836
|
...contentByFile === undefined ? {} : { onContent: (relPath, content) => contentByFile.set(relPath, content) },
|
|
178844
|
-
...options.adapters === undefined ? {} : { adapters: options.adapters }
|
|
179837
|
+
...options.adapters === undefined ? {} : { adapters: options.adapters },
|
|
179838
|
+
...scanners === undefined ? {} : { scanners }
|
|
179839
|
+
};
|
|
179840
|
+
}
|
|
179841
|
+
function readDocFiles(inputs, options) {
|
|
179842
|
+
const collectDocs = options.collectDocs ?? collectFiles;
|
|
179843
|
+
return collectDocs(options.projectRoot, inputs.config.include.docs).map((relPath) => {
|
|
179844
|
+
const read = inputs.readFile(relPath);
|
|
179845
|
+
if (read.ok) {
|
|
179846
|
+
inputs.contentByFile?.set(relPath, read.content);
|
|
179847
|
+
}
|
|
179848
|
+
return { relPath, read };
|
|
178845
179849
|
});
|
|
178846
|
-
|
|
179850
|
+
}
|
|
179851
|
+
function finishProjectScan(inputs, codeScan, docReads, buildGraph) {
|
|
179852
|
+
const diagnostics = [...inputs.diagnostics, ...codeScan.diagnostics];
|
|
178847
179853
|
const docFiles = [];
|
|
178848
|
-
for (const relPath of
|
|
178849
|
-
const read = readFile(relPath);
|
|
179854
|
+
for (const { relPath, read } of docReads) {
|
|
178850
179855
|
if (!read.ok) {
|
|
178851
179856
|
diagnostics.push(read.diagnostic);
|
|
178852
179857
|
continue;
|
|
178853
179858
|
}
|
|
178854
|
-
contentByFile?.set(relPath, read.content);
|
|
178855
179859
|
const scan2 = scanMarkdown(relPath, read.content);
|
|
178856
179860
|
diagnostics.push(...scan2.diagnostics);
|
|
178857
179861
|
docFiles.push(scan2);
|
|
178858
179862
|
}
|
|
178859
179863
|
const applied = applyLinkManifest({
|
|
178860
|
-
manifest:
|
|
179864
|
+
manifest: inputs.manifest,
|
|
178861
179865
|
codeFiles: codeScan.codeFiles,
|
|
178862
179866
|
docFiles,
|
|
178863
179867
|
scanDiagnostics: diagnostics
|
|
@@ -178869,25 +179873,26 @@ function scanProject(options) {
|
|
|
178869
179873
|
docFiles: applied.docFiles,
|
|
178870
179874
|
diagnostics
|
|
178871
179875
|
};
|
|
178872
|
-
if (contentByFile !== undefined) {
|
|
178873
|
-
scan.contentByFile = contentByFile;
|
|
179876
|
+
if (inputs.contentByFile !== undefined) {
|
|
179877
|
+
scan.contentByFile = inputs.contentByFile;
|
|
178874
179878
|
}
|
|
178875
|
-
if (
|
|
179879
|
+
if (buildGraph === true) {
|
|
178876
179880
|
scan.graph = buildLinkGraph(codeFiles, applied.docFiles);
|
|
178877
179881
|
}
|
|
178878
|
-
return
|
|
178879
|
-
ok: true,
|
|
178880
|
-
scan
|
|
178881
|
-
};
|
|
179882
|
+
return scan;
|
|
178882
179883
|
}
|
|
178883
179884
|
|
|
178884
179885
|
// src/lsp/project.ts
|
|
178885
179886
|
class Project {
|
|
178886
179887
|
projectRoot;
|
|
179888
|
+
options;
|
|
178887
179889
|
overlay = new Map;
|
|
179890
|
+
overlayRevision = 0;
|
|
178888
179891
|
current = emptyState();
|
|
178889
|
-
|
|
179892
|
+
cache = emptyCodeScanCache();
|
|
179893
|
+
constructor(projectRoot, options = {}) {
|
|
178890
179894
|
this.projectRoot = projectRoot;
|
|
179895
|
+
this.options = options;
|
|
178891
179896
|
}
|
|
178892
179897
|
get root() {
|
|
178893
179898
|
return this.projectRoot;
|
|
@@ -178897,48 +179902,57 @@ class Project {
|
|
|
178897
179902
|
}
|
|
178898
179903
|
setOverlay(relPath, content) {
|
|
178899
179904
|
this.overlay.set(relPath, content);
|
|
179905
|
+
this.overlayRevision += 1;
|
|
178900
179906
|
}
|
|
178901
179907
|
clearOverlay(relPath) {
|
|
178902
179908
|
this.overlay.delete(relPath);
|
|
179909
|
+
this.overlayRevision += 1;
|
|
178903
179910
|
}
|
|
178904
179911
|
resolve() {
|
|
178905
179912
|
const outcome = scanProject({
|
|
178906
|
-
|
|
178907
|
-
collectCode: (_projectRoot, include) => this.collectCode(include),
|
|
178908
|
-
collectDocs: (_projectRoot, patterns) => this.collect(patterns, false),
|
|
178909
|
-
readFile: (relPath) => this.readContent(relPath),
|
|
179913
|
+
...this.scanSources(this.overlay),
|
|
178910
179914
|
buildGraph: true,
|
|
178911
179915
|
keepContent: true
|
|
178912
179916
|
});
|
|
178913
|
-
|
|
178914
|
-
|
|
178915
|
-
|
|
178916
|
-
|
|
178917
|
-
|
|
179917
|
+
this.current = resolvedState(outcome);
|
|
179918
|
+
return this.current;
|
|
179919
|
+
}
|
|
179920
|
+
resolveAsync() {
|
|
179921
|
+
const revision = this.overlayRevision;
|
|
179922
|
+
const scan = scanProjectAsync({
|
|
179923
|
+
...this.scanSources(new Map(this.overlay)),
|
|
179924
|
+
cache: this.cache,
|
|
179925
|
+
onConfigurationChange: clearRuntimeProbeCache
|
|
179926
|
+
});
|
|
179927
|
+
let cancelled = false;
|
|
179928
|
+
const promise = scan.promise.then((outcome) => {
|
|
179929
|
+
if (cancelled || revision !== this.overlayRevision) {
|
|
179930
|
+
throw abortError();
|
|
179931
|
+
}
|
|
179932
|
+
if (outcome.ok) {
|
|
179933
|
+
this.cache = outcome.cache;
|
|
179934
|
+
}
|
|
179935
|
+
this.current = resolvedState(outcome);
|
|
178918
179936
|
return this.current;
|
|
178919
|
-
}
|
|
178920
|
-
const {
|
|
178921
|
-
codeFiles,
|
|
178922
|
-
docFiles,
|
|
178923
|
-
diagnostics: scanDiagnostics,
|
|
178924
|
-
contentByFile,
|
|
178925
|
-
graph
|
|
178926
|
-
} = outcome.scan;
|
|
178927
|
-
const relationship = resolveLinks({
|
|
178928
|
-
codeFiles,
|
|
178929
|
-
docFiles,
|
|
178930
|
-
scanDiagnostics,
|
|
178931
|
-
audit: false
|
|
178932
179937
|
});
|
|
178933
|
-
|
|
178934
|
-
|
|
178935
|
-
|
|
178936
|
-
|
|
178937
|
-
|
|
179938
|
+
return {
|
|
179939
|
+
promise,
|
|
179940
|
+
cancel() {
|
|
179941
|
+
cancelled = true;
|
|
179942
|
+
scan.cancel();
|
|
179943
|
+
}
|
|
178938
179944
|
};
|
|
178939
|
-
return this.current;
|
|
178940
179945
|
}
|
|
178941
|
-
|
|
179946
|
+
scanSources(overlay) {
|
|
179947
|
+
return {
|
|
179948
|
+
projectRoot: this.projectRoot,
|
|
179949
|
+
collectCode: (_projectRoot, include) => this.collectCode(include, overlay),
|
|
179950
|
+
collectDocs: (_projectRoot, patterns) => this.collectDocs(patterns, overlay),
|
|
179951
|
+
readFile: (relPath) => this.readContent(relPath, overlay),
|
|
179952
|
+
...this.options.adapters === undefined ? {} : { adapters: this.options.adapters }
|
|
179953
|
+
};
|
|
179954
|
+
}
|
|
179955
|
+
collectCode(codeInclude, overlay) {
|
|
178942
179956
|
const onDisk = collectCodeFiles(this.projectRoot, codeInclude);
|
|
178943
179957
|
const seen = new Set(onDisk.map((file) => file.relPath));
|
|
178944
179958
|
const all = [...onDisk];
|
|
@@ -178947,11 +179961,11 @@ class Project {
|
|
|
178947
179961
|
if (entry === undefined) {
|
|
178948
179962
|
continue;
|
|
178949
179963
|
}
|
|
178950
|
-
for (const relPath of
|
|
179964
|
+
for (const relPath of overlay.keys()) {
|
|
178951
179965
|
if (seen.has(relPath)) {
|
|
178952
179966
|
continue;
|
|
178953
179967
|
}
|
|
178954
|
-
if (language
|
|
179968
|
+
if (hasExcludedSuffix(language, relPath)) {
|
|
178955
179969
|
continue;
|
|
178956
179970
|
}
|
|
178957
179971
|
if (entry.patterns.some((pattern) => matchGlob(pattern, relPath))) {
|
|
@@ -178962,22 +179976,19 @@ class Project {
|
|
|
178962
179976
|
}
|
|
178963
179977
|
return all.toSorted((left, right) => comparePaths(left.relPath, right.relPath));
|
|
178964
179978
|
}
|
|
178965
|
-
readContent(relPath) {
|
|
178966
|
-
const overlaid =
|
|
179979
|
+
readContent(relPath, overlay) {
|
|
179980
|
+
const overlaid = overlay.get(relPath);
|
|
178967
179981
|
if (overlaid !== undefined) {
|
|
178968
179982
|
return { ok: true, content: overlaid };
|
|
178969
179983
|
}
|
|
178970
179984
|
return readManagedFile(this.projectRoot, relPath);
|
|
178971
179985
|
}
|
|
178972
|
-
|
|
179986
|
+
collectDocs(patterns, overlay) {
|
|
178973
179987
|
const paths = new Set(collectFiles(this.projectRoot, patterns));
|
|
178974
|
-
for (const relPath of
|
|
179988
|
+
for (const relPath of overlay.keys()) {
|
|
178975
179989
|
if (paths.has(relPath)) {
|
|
178976
179990
|
continue;
|
|
178977
179991
|
}
|
|
178978
|
-
if (isCode && relPath.endsWith(".d.ts")) {
|
|
178979
|
-
continue;
|
|
178980
|
-
}
|
|
178981
179992
|
if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
|
|
178982
179993
|
paths.add(relPath);
|
|
178983
179994
|
}
|
|
@@ -178985,6 +179996,19 @@ class Project {
|
|
|
178985
179996
|
return [...paths].toSorted(comparePaths);
|
|
178986
179997
|
}
|
|
178987
179998
|
}
|
|
179999
|
+
function resolvedState(outcome) {
|
|
180000
|
+
if (!outcome.ok) {
|
|
180001
|
+
return { ...emptyState(), diagnostics: sortDiagnostics(outcome.diagnostics) };
|
|
180002
|
+
}
|
|
180003
|
+
const { codeFiles, docFiles, diagnostics: scanDiagnostics, contentByFile, graph } = outcome.scan;
|
|
180004
|
+
const relationship = resolveLinks({ codeFiles, docFiles, scanDiagnostics, audit: false });
|
|
180005
|
+
return {
|
|
180006
|
+
graph,
|
|
180007
|
+
index: buildPositionIndex(graph),
|
|
180008
|
+
diagnostics: sortDiagnostics([...scanDiagnostics, ...relationship]),
|
|
180009
|
+
contentByFile
|
|
180010
|
+
};
|
|
180011
|
+
}
|
|
178988
180012
|
function emptyState() {
|
|
178989
180013
|
const graph = buildLinkGraph([], []);
|
|
178990
180014
|
return {
|
|
@@ -178995,6 +180019,74 @@ function emptyState() {
|
|
|
178995
180019
|
};
|
|
178996
180020
|
}
|
|
178997
180021
|
|
|
180022
|
+
// src/lsp/scheduler.ts
|
|
180023
|
+
class RescanScheduler {
|
|
180024
|
+
options;
|
|
180025
|
+
generation = 0;
|
|
180026
|
+
dirty = false;
|
|
180027
|
+
debounce;
|
|
180028
|
+
active;
|
|
180029
|
+
stopped = false;
|
|
180030
|
+
constructor(options) {
|
|
180031
|
+
this.options = options;
|
|
180032
|
+
}
|
|
180033
|
+
request(when) {
|
|
180034
|
+
if (this.stopped) {
|
|
180035
|
+
return;
|
|
180036
|
+
}
|
|
180037
|
+
this.generation += 1;
|
|
180038
|
+
this.dirty = true;
|
|
180039
|
+
this.active?.task.cancel();
|
|
180040
|
+
this.clearDebounce();
|
|
180041
|
+
if (when === "debounced") {
|
|
180042
|
+
const handle = this.options.timers.setTimeout(() => {
|
|
180043
|
+
this.debounce = undefined;
|
|
180044
|
+
this.startIfReady();
|
|
180045
|
+
}, this.options.debounceMs);
|
|
180046
|
+
this.debounce = { handle };
|
|
180047
|
+
return;
|
|
180048
|
+
}
|
|
180049
|
+
this.startIfReady();
|
|
180050
|
+
}
|
|
180051
|
+
stop() {
|
|
180052
|
+
this.stopped = true;
|
|
180053
|
+
this.clearDebounce();
|
|
180054
|
+
this.active?.task.cancel();
|
|
180055
|
+
}
|
|
180056
|
+
startIfReady() {
|
|
180057
|
+
if (this.stopped || !this.dirty || this.active !== undefined || this.debounce !== undefined) {
|
|
180058
|
+
return;
|
|
180059
|
+
}
|
|
180060
|
+
this.dirty = false;
|
|
180061
|
+
const active = { generation: this.generation, task: this.options.scan() };
|
|
180062
|
+
this.active = active;
|
|
180063
|
+
active.task.promise.then((state) => this.settle(active, () => this.accept(active, state)), (error) => this.settle(active, () => this.fail(error)));
|
|
180064
|
+
}
|
|
180065
|
+
settle(active, report) {
|
|
180066
|
+
if (this.active === active) {
|
|
180067
|
+
this.active = undefined;
|
|
180068
|
+
}
|
|
180069
|
+
report();
|
|
180070
|
+
this.startIfReady();
|
|
180071
|
+
}
|
|
180072
|
+
accept(active, state) {
|
|
180073
|
+
if (!this.stopped && active.generation === this.generation) {
|
|
180074
|
+
this.options.onState(state);
|
|
180075
|
+
}
|
|
180076
|
+
}
|
|
180077
|
+
fail(error) {
|
|
180078
|
+
if (!isAbortError(error)) {
|
|
180079
|
+
this.options.onError(error);
|
|
180080
|
+
}
|
|
180081
|
+
}
|
|
180082
|
+
clearDebounce() {
|
|
180083
|
+
if (this.debounce !== undefined) {
|
|
180084
|
+
this.options.timers.clearTimeout(this.debounce.handle);
|
|
180085
|
+
this.debounce = undefined;
|
|
180086
|
+
}
|
|
180087
|
+
}
|
|
180088
|
+
}
|
|
180089
|
+
|
|
178998
180090
|
// src/lsp/transport.ts
|
|
178999
180091
|
var HEADER_SEPARATOR = `\r
|
|
179000
180092
|
\r
|
|
@@ -179062,6 +180154,10 @@ function parseContentLength(headerText) {
|
|
|
179062
180154
|
}
|
|
179063
180155
|
|
|
179064
180156
|
// src/lsp/server.ts
|
|
180157
|
+
var GLOBAL_TIMERS = {
|
|
180158
|
+
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
180159
|
+
clearTimeout: (handle) => clearTimeout(handle)
|
|
180160
|
+
};
|
|
179065
180161
|
var CAPABILITIES = {
|
|
179066
180162
|
textDocumentSync: 1,
|
|
179067
180163
|
hoverProvider: true,
|
|
@@ -179072,44 +180168,55 @@ var CAPABILITIES = {
|
|
|
179072
180168
|
class Server {
|
|
179073
180169
|
send;
|
|
179074
180170
|
project = null;
|
|
180171
|
+
scheduler = null;
|
|
179075
180172
|
openFiles = new Set;
|
|
179076
|
-
dirty = true;
|
|
179077
|
-
timer = null;
|
|
179078
180173
|
shuttingDown = false;
|
|
179079
180174
|
makeProject;
|
|
179080
180175
|
debounceMs;
|
|
179081
180176
|
onExit;
|
|
180177
|
+
timers;
|
|
180178
|
+
onScanError;
|
|
179082
180179
|
constructor(send, options = {}) {
|
|
179083
180180
|
this.send = send;
|
|
179084
180181
|
this.makeProject = options.makeProject ?? ((root) => new Project(root));
|
|
179085
180182
|
this.debounceMs = options.debounceMs ?? 50;
|
|
179086
180183
|
this.onExit = options.onExit ?? ((code) => process.exit(code));
|
|
180184
|
+
this.timers = options.timers ?? GLOBAL_TIMERS;
|
|
180185
|
+
this.onScanError = options.onScanError ?? ((error) => process.stderr.write(`docbridge lsp: scan failed: ${reasonOf(error)}
|
|
180186
|
+
`));
|
|
179087
180187
|
}
|
|
179088
180188
|
handle(message) {
|
|
179089
180189
|
switch (message.method) {
|
|
179090
180190
|
case "initialize":
|
|
179091
180191
|
this.respond(message.id, { capabilities: CAPABILITIES });
|
|
179092
|
-
this.
|
|
180192
|
+
this.start(this.makeProject(resolveRoot(message.params)));
|
|
179093
180193
|
break;
|
|
179094
180194
|
case "initialized":
|
|
179095
|
-
this.
|
|
180195
|
+
this.scheduler?.request("now");
|
|
179096
180196
|
break;
|
|
179097
180197
|
case "shutdown":
|
|
179098
180198
|
this.shuttingDown = true;
|
|
179099
|
-
this.
|
|
180199
|
+
this.scheduler?.stop();
|
|
179100
180200
|
this.respond(message.id, null);
|
|
179101
180201
|
break;
|
|
179102
180202
|
case "exit":
|
|
180203
|
+
this.scheduler?.stop();
|
|
179103
180204
|
this.onExit(this.shuttingDown ? 0 : 1);
|
|
179104
180205
|
break;
|
|
179105
180206
|
case "textDocument/didOpen":
|
|
179106
|
-
this.
|
|
180207
|
+
if (!this.shuttingDown) {
|
|
180208
|
+
this.onDidOpen(message.params);
|
|
180209
|
+
}
|
|
179107
180210
|
break;
|
|
179108
180211
|
case "textDocument/didChange":
|
|
179109
|
-
this.
|
|
180212
|
+
if (!this.shuttingDown) {
|
|
180213
|
+
this.onDidChange(message.params);
|
|
180214
|
+
}
|
|
179110
180215
|
break;
|
|
179111
180216
|
case "textDocument/didClose":
|
|
179112
|
-
this.
|
|
180217
|
+
if (!this.shuttingDown) {
|
|
180218
|
+
this.onDidClose(message.params);
|
|
180219
|
+
}
|
|
179113
180220
|
break;
|
|
179114
180221
|
case "textDocument/hover":
|
|
179115
180222
|
this.respond(message.id, this.onHover(message.params));
|
|
@@ -179126,6 +180233,16 @@ class Server {
|
|
|
179126
180233
|
}
|
|
179127
180234
|
}
|
|
179128
180235
|
}
|
|
180236
|
+
start(project) {
|
|
180237
|
+
this.project = project;
|
|
180238
|
+
this.scheduler = new RescanScheduler({
|
|
180239
|
+
scan: () => project.resolveAsync(),
|
|
180240
|
+
onState: (state) => this.publishOpenFiles(state),
|
|
180241
|
+
onError: this.onScanError,
|
|
180242
|
+
debounceMs: this.debounceMs,
|
|
180243
|
+
timers: this.timers
|
|
180244
|
+
});
|
|
180245
|
+
}
|
|
179129
180246
|
onDidOpen(params) {
|
|
179130
180247
|
const doc = textDocument(params);
|
|
179131
180248
|
const rel = this.relPath(doc?.uri);
|
|
@@ -179135,8 +180252,7 @@ class Server {
|
|
|
179135
180252
|
}
|
|
179136
180253
|
this.openFiles.add(rel);
|
|
179137
180254
|
this.project.setOverlay(rel, text);
|
|
179138
|
-
this.
|
|
179139
|
-
this.flush();
|
|
180255
|
+
this.scheduler?.request("now");
|
|
179140
180256
|
}
|
|
179141
180257
|
onDidChange(params) {
|
|
179142
180258
|
const record = params;
|
|
@@ -179147,7 +180263,7 @@ class Server {
|
|
|
179147
180263
|
return;
|
|
179148
180264
|
}
|
|
179149
180265
|
this.project.setOverlay(rel, text);
|
|
179150
|
-
this.
|
|
180266
|
+
this.scheduler?.request("debounced");
|
|
179151
180267
|
}
|
|
179152
180268
|
onDidClose(params) {
|
|
179153
180269
|
const doc = textDocument(params);
|
|
@@ -179157,9 +180273,8 @@ class Server {
|
|
|
179157
180273
|
}
|
|
179158
180274
|
this.openFiles.delete(rel);
|
|
179159
180275
|
this.project.clearOverlay(rel);
|
|
179160
|
-
this.dirty = true;
|
|
179161
180276
|
this.publish(rel, []);
|
|
179162
|
-
this.
|
|
180277
|
+
this.scheduler?.request("now");
|
|
179163
180278
|
}
|
|
179164
180279
|
onHover(params) {
|
|
179165
180280
|
const located = this.locate(params);
|
|
@@ -179198,7 +180313,6 @@ class Server {
|
|
|
179198
180313
|
if (rel === undefined || this.project === null || typeof position?.line !== "number" || typeof position?.character !== "number") {
|
|
179199
180314
|
return null;
|
|
179200
180315
|
}
|
|
179201
|
-
this.flush();
|
|
179202
180316
|
return {
|
|
179203
180317
|
state: this.project.state,
|
|
179204
180318
|
rel,
|
|
@@ -179206,28 +180320,14 @@ class Server {
|
|
|
179206
180320
|
position: fromLspPosition({ line: position.line, character: position.character })
|
|
179207
180321
|
};
|
|
179208
180322
|
}
|
|
179209
|
-
|
|
179210
|
-
this.dirty = true;
|
|
179211
|
-
this.clearTimer();
|
|
179212
|
-
this.timer = setTimeout(() => {
|
|
179213
|
-
this.timer = null;
|
|
179214
|
-
this.flush();
|
|
179215
|
-
}, this.debounceMs);
|
|
179216
|
-
}
|
|
179217
|
-
flush() {
|
|
179218
|
-
this.clearTimer();
|
|
179219
|
-
if (this.project === null || !this.dirty) {
|
|
179220
|
-
return;
|
|
179221
|
-
}
|
|
179222
|
-
this.dirty = false;
|
|
179223
|
-
const state = this.project.resolve();
|
|
180323
|
+
publishOpenFiles(state) {
|
|
179224
180324
|
for (const rel of this.openFiles) {
|
|
179225
180325
|
const content = state.contentByFile.get(rel) ?? "";
|
|
179226
180326
|
this.publish(rel, diagnosticsForFile(state.diagnostics, rel, content));
|
|
179227
180327
|
}
|
|
179228
180328
|
}
|
|
179229
180329
|
publish(rel, diagnostics) {
|
|
179230
|
-
if (this.project === null) {
|
|
180330
|
+
if (this.project === null || this.shuttingDown) {
|
|
179231
180331
|
return;
|
|
179232
180332
|
}
|
|
179233
180333
|
this.send({
|
|
@@ -179248,12 +180348,6 @@ class Server {
|
|
|
179248
180348
|
}
|
|
179249
180349
|
this.send({ jsonrpc: "2.0", id, result });
|
|
179250
180350
|
}
|
|
179251
|
-
clearTimer() {
|
|
179252
|
-
if (this.timer !== null) {
|
|
179253
|
-
clearTimeout(this.timer);
|
|
179254
|
-
this.timer = null;
|
|
179255
|
-
}
|
|
179256
|
-
}
|
|
179257
180351
|
}
|
|
179258
180352
|
function resolveRoot(params) {
|
|
179259
180353
|
const record = params;
|
|
@@ -179347,7 +180441,7 @@ function check(options) {
|
|
|
179347
180441
|
}
|
|
179348
180442
|
|
|
179349
180443
|
// src/query/related.ts
|
|
179350
|
-
import { isAbsolute as
|
|
180444
|
+
import { isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
|
|
179351
180445
|
function normalizeChangedPaths(projectRoot, paths) {
|
|
179352
180446
|
const normalized = new Set;
|
|
179353
180447
|
for (const raw of paths) {
|
|
@@ -179355,7 +180449,7 @@ function normalizeChangedPaths(projectRoot, paths) {
|
|
|
179355
180449
|
if (trimmed === "") {
|
|
179356
180450
|
continue;
|
|
179357
180451
|
}
|
|
179358
|
-
const relativized =
|
|
180452
|
+
const relativized = isAbsolute3(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
|
|
179359
180453
|
normalized.add(relativized.replace(/^(\.\/)+/, ""));
|
|
179360
180454
|
}
|
|
179361
180455
|
return [...normalized];
|
|
@@ -179777,7 +180871,7 @@ function comparePairs(left, right) {
|
|
|
179777
180871
|
// src/setup/update-cache.ts
|
|
179778
180872
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
179779
180873
|
import { homedir } from "node:os";
|
|
179780
|
-
import { dirname as dirname3, join as
|
|
180874
|
+
import { dirname as dirname3, join as join8 } from "node:path";
|
|
179781
180875
|
var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
179782
180876
|
var UPDATE_CACHE_SCHEMA_VERSION = 1;
|
|
179783
180877
|
function resolveUpdateCachePath(env = process.env, home = homedir()) {
|
|
@@ -179785,8 +180879,8 @@ function resolveUpdateCachePath(env = process.env, home = homedir()) {
|
|
|
179785
180879
|
if (explicit !== undefined && explicit.length > 0) {
|
|
179786
180880
|
return explicit;
|
|
179787
180881
|
}
|
|
179788
|
-
const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME :
|
|
179789
|
-
return
|
|
180882
|
+
const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join8(home, ".cache");
|
|
180883
|
+
return join8(cacheRoot, "docbridge", "update-check.json");
|
|
179790
180884
|
}
|
|
179791
180885
|
function readUpdateCache(path) {
|
|
179792
180886
|
let rawText;
|
|
@@ -180030,7 +181124,7 @@ function isEnabledFlag(value) {
|
|
|
180030
181124
|
|
|
180031
181125
|
// src/cli/docs.ts
|
|
180032
181126
|
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
180033
|
-
import { join as
|
|
181127
|
+
import { join as join9 } from "node:path";
|
|
180034
181128
|
|
|
180035
181129
|
// src/cli/errors.ts
|
|
180036
181130
|
class CliError extends Error {
|
|
@@ -180201,7 +181295,7 @@ function parseDocument(name, source) {
|
|
|
180201
181295
|
};
|
|
180202
181296
|
}
|
|
180203
181297
|
function createFileDocumentationReader(packageRoot) {
|
|
180204
|
-
const docsRoot =
|
|
181298
|
+
const docsRoot = join9(packageRoot, "docs", "user");
|
|
180205
181299
|
function documents() {
|
|
180206
181300
|
let fileNames;
|
|
180207
181301
|
try {
|
|
@@ -180214,7 +181308,7 @@ function createFileDocumentationReader(packageRoot) {
|
|
|
180214
181308
|
}
|
|
180215
181309
|
return fileNames.map((fileName) => {
|
|
180216
181310
|
const name = fileName.slice(0, -".md".length);
|
|
180217
|
-
return parseDocument(name, readFileSync5(
|
|
181311
|
+
return parseDocument(name, readFileSync5(join9(docsRoot, fileName), "utf8"));
|
|
180218
181312
|
});
|
|
180219
181313
|
}
|
|
180220
181314
|
return {
|
|
@@ -180611,12 +181705,12 @@ function hasHelpFlag(args) {
|
|
|
180611
181705
|
}
|
|
180612
181706
|
|
|
180613
181707
|
// src/cli/init.ts
|
|
180614
|
-
import { readFileSync as readFileSync8, statSync as
|
|
180615
|
-
import { join as
|
|
181708
|
+
import { readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
181709
|
+
import { join as join13, resolve as resolve3 } from "node:path";
|
|
180616
181710
|
|
|
180617
181711
|
// src/setup/init-discovery.ts
|
|
180618
|
-
import { existsSync as
|
|
180619
|
-
import { basename, dirname as dirname4, join as
|
|
181712
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
181713
|
+
import { basename, dirname as dirname4, join as join10 } from "node:path";
|
|
180620
181714
|
var HIGH_CONFIDENCE_DIR_NAMES = new Set([
|
|
180621
181715
|
"docs/specs",
|
|
180622
181716
|
"specs",
|
|
@@ -180646,22 +181740,42 @@ var EXCLUDED_DIR_SEGMENTS = new Set([
|
|
|
180646
181740
|
"contributing"
|
|
180647
181741
|
]);
|
|
180648
181742
|
var IGNORED_WALK_SEGMENTS = new Set(["node_modules", ".git", "dist", "build"]);
|
|
180649
|
-
var
|
|
180650
|
-
|
|
180651
|
-
|
|
180652
|
-
"packages/*/src/**/*.ts",
|
|
180653
|
-
"apps/*/src/**/*.ts"
|
|
180654
|
-
];
|
|
181743
|
+
var SCRIPT_SOURCE_ROOTS = ["src", "lib", "packages/*/src", "apps/*/src"];
|
|
181744
|
+
var TYPESCRIPT_PATTERNS = scriptPatterns("typescript");
|
|
181745
|
+
var JAVASCRIPT_PATTERNS = scriptPatterns("javascript");
|
|
180655
181746
|
var SWIFT_PATTERNS = ["Sources/**/*.swift", "*/Sources/**/*.swift"];
|
|
180656
181747
|
var DART_PATTERNS = ["lib/**/*.dart"];
|
|
180657
181748
|
var RUST_PATTERNS = ["src/**/*.rs", "*/src/**/*.rs"];
|
|
180658
181749
|
var GO_PATTERNS = ["*.go", "cmd/**/*.go", "internal/**/*.go", "pkg/**/*.go"];
|
|
181750
|
+
var RUBY_PATTERNS = ["lib/**/*.rb", "app/**/*.rb"];
|
|
181751
|
+
var JAVA_PATTERNS = ["src/main/java/**/*.java", "src/**/*.java"];
|
|
180659
181752
|
var LANGUAGE_PATTERNS = {
|
|
180660
181753
|
typescript: TYPESCRIPT_PATTERNS,
|
|
180661
181754
|
swift: SWIFT_PATTERNS,
|
|
180662
181755
|
dart: DART_PATTERNS,
|
|
180663
181756
|
rust: RUST_PATTERNS,
|
|
180664
|
-
go: GO_PATTERNS
|
|
181757
|
+
go: GO_PATTERNS,
|
|
181758
|
+
javascript: JAVASCRIPT_PATTERNS,
|
|
181759
|
+
ruby: RUBY_PATTERNS,
|
|
181760
|
+
java: JAVA_PATTERNS
|
|
181761
|
+
};
|
|
181762
|
+
var EXCLUDED_CODE_SEGMENTS = {
|
|
181763
|
+
typescript: ["__tests__", "tests", "test"],
|
|
181764
|
+
swift: ["Tests", "tests"],
|
|
181765
|
+
dart: ["test"],
|
|
181766
|
+
rust: ["target", "tests", "benches", "examples"],
|
|
181767
|
+
go: ["vendor", "testdata"],
|
|
181768
|
+
javascript: ["__tests__", "tests", "test"],
|
|
181769
|
+
python: ["tests", "venv", "dist", "site-packages"],
|
|
181770
|
+
ruby: ["spec", "test", "vendor"],
|
|
181771
|
+
java: ["target", "src/test"]
|
|
181772
|
+
};
|
|
181773
|
+
var TEST_FILE_PATTERNS = {
|
|
181774
|
+
typescript: /\.(?:test|spec)\.[cm]?tsx?$/u,
|
|
181775
|
+
swift: /tests\.swift$/u,
|
|
181776
|
+
dart: /_test\.dart$/u,
|
|
181777
|
+
go: /_test\.go$/u,
|
|
181778
|
+
javascript: /\.(?:test|spec)\.[cm]?jsx?$/u
|
|
180665
181779
|
};
|
|
180666
181780
|
function discoverRepository(projectRoot) {
|
|
180667
181781
|
return {
|
|
@@ -180745,8 +181859,8 @@ function discoverCodeScope(projectRoot) {
|
|
|
180745
181859
|
};
|
|
180746
181860
|
}
|
|
180747
181861
|
function discoverAgentTarget(projectRoot) {
|
|
180748
|
-
const hasAgentsDir =
|
|
180749
|
-
const hasClaudeDir =
|
|
181862
|
+
const hasAgentsDir = existsSync4(join10(projectRoot, ".agents"));
|
|
181863
|
+
const hasClaudeDir = existsSync4(join10(projectRoot, ".claude"));
|
|
180750
181864
|
let defaultTarget = "none";
|
|
180751
181865
|
let recommendedTarget = "none";
|
|
180752
181866
|
let message;
|
|
@@ -180773,7 +181887,7 @@ function discoverAgentTarget(projectRoot) {
|
|
|
180773
181887
|
};
|
|
180774
181888
|
}
|
|
180775
181889
|
function collectMarkdownFiles(projectRoot, currentDir = ".") {
|
|
180776
|
-
const absoluteDir =
|
|
181890
|
+
const absoluteDir = join10(projectRoot, currentDir);
|
|
180777
181891
|
let entries;
|
|
180778
181892
|
try {
|
|
180779
181893
|
entries = readdirSync3(absoluteDir);
|
|
@@ -180787,10 +181901,10 @@ function collectMarkdownFiles(projectRoot, currentDir = ".") {
|
|
|
180787
181901
|
if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
|
|
180788
181902
|
continue;
|
|
180789
181903
|
}
|
|
180790
|
-
const absolutePath =
|
|
181904
|
+
const absolutePath = join10(projectRoot, relPath);
|
|
180791
181905
|
let stats;
|
|
180792
181906
|
try {
|
|
180793
|
-
stats =
|
|
181907
|
+
stats = statSync3(absolutePath);
|
|
180794
181908
|
} catch {
|
|
180795
181909
|
continue;
|
|
180796
181910
|
}
|
|
@@ -180828,8 +181942,23 @@ function scoreDocsDirectory(directory) {
|
|
|
180828
181942
|
}
|
|
180829
181943
|
return score;
|
|
180830
181944
|
}
|
|
181945
|
+
function scriptPatterns(language) {
|
|
181946
|
+
return SCRIPT_SOURCE_ROOTS.flatMap((root) => LANGUAGE_SUFFIXES[language].map((suffix) => `${root}/**/*${suffix}`));
|
|
181947
|
+
}
|
|
180831
181948
|
function activeCodePatterns(projectRoot, language) {
|
|
180832
|
-
|
|
181949
|
+
const candidates = language === "python" ? pythonPatterns(projectRoot) : LANGUAGE_PATTERNS[language];
|
|
181950
|
+
const active = candidates.filter((pattern) => countCodeFiles(projectRoot, [pattern], language) > 0);
|
|
181951
|
+
return language === "java" ? active.slice(0, 1) : active;
|
|
181952
|
+
}
|
|
181953
|
+
function pythonPatterns(projectRoot) {
|
|
181954
|
+
let entries;
|
|
181955
|
+
try {
|
|
181956
|
+
entries = readdirSync3(projectRoot, { withFileTypes: true });
|
|
181957
|
+
} catch {
|
|
181958
|
+
return [];
|
|
181959
|
+
}
|
|
181960
|
+
const packages = entries.filter((entry) => entry.isDirectory() && entry.name !== "src" && !entry.name.startsWith(".") && !IGNORED_WALK_SEGMENTS.has(entry.name) && existsSync4(join10(projectRoot, entry.name, "__init__.py"))).map((entry) => entry.name).toSorted();
|
|
181961
|
+
return ["src/**/*.py", ...packages.map((name) => `${name}/**/*.py`)];
|
|
180833
181962
|
}
|
|
180834
181963
|
function countCodeFiles(projectRoot, patterns, language) {
|
|
180835
181964
|
const seen = new Set;
|
|
@@ -180845,33 +181974,9 @@ function countCodeFiles(projectRoot, patterns, language) {
|
|
|
180845
181974
|
function isExcludedCodeFile(filePath, language) {
|
|
180846
181975
|
const lower = filePath.toLowerCase();
|
|
180847
181976
|
const segments = filePath.split("/");
|
|
180848
|
-
|
|
180849
|
-
|
|
180850
|
-
|
|
180851
|
-
}
|
|
180852
|
-
if (lower.endsWith(".test.ts") || lower.endsWith(".spec.ts") || segments.includes("__tests__") || segments.includes("tests") || segments.includes("test")) {
|
|
180853
|
-
return true;
|
|
180854
|
-
}
|
|
180855
|
-
}
|
|
180856
|
-
if (language === "swift") {
|
|
180857
|
-
if (lower.endsWith("tests.swift") || segments.includes("Tests") || segments.includes("tests")) {
|
|
180858
|
-
return true;
|
|
180859
|
-
}
|
|
180860
|
-
}
|
|
180861
|
-
if (language === "dart") {
|
|
180862
|
-
if (lower.endsWith("_test.dart") || segments.includes("test")) {
|
|
180863
|
-
return true;
|
|
180864
|
-
}
|
|
180865
|
-
}
|
|
180866
|
-
if (language === "rust") {
|
|
180867
|
-
if (segments.includes("target") || segments.includes("tests") || segments.includes("benches") || segments.includes("examples")) {
|
|
180868
|
-
return true;
|
|
180869
|
-
}
|
|
180870
|
-
}
|
|
180871
|
-
if (language === "go") {
|
|
180872
|
-
if (lower.endsWith("_test.go") || segments.includes("vendor") || segments.includes("testdata")) {
|
|
180873
|
-
return true;
|
|
180874
|
-
}
|
|
181977
|
+
const delimited = `/${filePath}/`;
|
|
181978
|
+
if (hasExcludedSuffix(language, lower) || TEST_FILE_PATTERNS[language]?.test(lower) === true || EXCLUDED_CODE_SEGMENTS[language].some((run) => delimited.includes(`/${run}/`))) {
|
|
181979
|
+
return true;
|
|
180875
181980
|
}
|
|
180876
181981
|
if (segments.some((segment) => ["generated", "gen", ".dart_tool", "build"].includes(segment.toLowerCase()))) {
|
|
180877
181982
|
return true;
|
|
@@ -180880,17 +181985,17 @@ function isExcludedCodeFile(filePath, language) {
|
|
|
180880
181985
|
}
|
|
180881
181986
|
|
|
180882
181987
|
// src/setup/init-plan.ts
|
|
180883
|
-
import { existsSync as
|
|
180884
|
-
import { join as
|
|
181988
|
+
import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
181989
|
+
import { join as join12, relative as relative3 } from "node:path";
|
|
180885
181990
|
|
|
180886
181991
|
// src/setup/skill-assets.ts
|
|
180887
181992
|
import { cpSync, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
180888
|
-
import { join as
|
|
181993
|
+
import { join as join11 } from "node:path";
|
|
180889
181994
|
function classifyManagedPath(projectRoot, relativePath) {
|
|
180890
181995
|
const segments = relativePath.split(/[/\\]/).filter((segment) => segment.length > 0);
|
|
180891
181996
|
let current = projectRoot;
|
|
180892
181997
|
for (const [index, segment] of segments.entries()) {
|
|
180893
|
-
current =
|
|
181998
|
+
current = join11(current, segment);
|
|
180894
181999
|
const isLast = index === segments.length - 1;
|
|
180895
182000
|
let stats;
|
|
180896
182001
|
try {
|
|
@@ -180937,7 +182042,7 @@ function compareSkillTree(installedDir, templateDir) {
|
|
|
180937
182042
|
continue;
|
|
180938
182043
|
}
|
|
180939
182044
|
installedFiles.delete(file);
|
|
180940
|
-
if (readFileOrUndefined(
|
|
182045
|
+
if (readFileOrUndefined(join11(installedDir, file)) !== readFileOrUndefined(join11(templateDir, file))) {
|
|
180941
182046
|
modified.push(file);
|
|
180942
182047
|
}
|
|
180943
182048
|
}
|
|
@@ -180948,7 +182053,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180948
182053
|
return;
|
|
180949
182054
|
}
|
|
180950
182055
|
const kind = classifyManagedPath(projectRoot, operation.path);
|
|
180951
|
-
const destinationDir =
|
|
182056
|
+
const destinationDir = join11(projectRoot, operation.path);
|
|
180952
182057
|
if (operation.action === "remove") {
|
|
180953
182058
|
if (kind === "directory") {
|
|
180954
182059
|
rmSync(destinationDir, { recursive: true, force: true });
|
|
@@ -180966,7 +182071,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180966
182071
|
rmSync(destinationDir, { recursive: true, force: true });
|
|
180967
182072
|
}
|
|
180968
182073
|
mkdirSync3(destinationDir, { recursive: true });
|
|
180969
|
-
cpSync(
|
|
182074
|
+
cpSync(join11(packageRoot, "templates", "skills", skillName), destinationDir, {
|
|
180970
182075
|
recursive: true,
|
|
180971
182076
|
force: true
|
|
180972
182077
|
});
|
|
@@ -180974,7 +182079,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180974
182079
|
function collectSkillFiles(root, prefix, files) {
|
|
180975
182080
|
let entries;
|
|
180976
182081
|
try {
|
|
180977
|
-
entries = readdirSync4(
|
|
182082
|
+
entries = readdirSync4(join11(root, prefix), { withFileTypes: true });
|
|
180978
182083
|
} catch {
|
|
180979
182084
|
return;
|
|
180980
182085
|
}
|
|
@@ -181085,14 +182190,14 @@ function buildConfigFromScope(scope) {
|
|
|
181085
182190
|
};
|
|
181086
182191
|
}
|
|
181087
182192
|
function listDistributableSkills(packageRoot) {
|
|
181088
|
-
const skillsRoot =
|
|
182193
|
+
const skillsRoot = join12(packageRoot, "templates", "skills");
|
|
181089
182194
|
let entries;
|
|
181090
182195
|
try {
|
|
181091
182196
|
entries = readdirSync5(skillsRoot);
|
|
181092
182197
|
} catch {
|
|
181093
182198
|
return [];
|
|
181094
182199
|
}
|
|
181095
|
-
return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) =>
|
|
182200
|
+
return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync5(join12(skillsRoot, entry, "SKILL.md"))).toSorted();
|
|
181096
182201
|
}
|
|
181097
182202
|
function formatInitPlan(plan) {
|
|
181098
182203
|
const lines = [];
|
|
@@ -181197,7 +182302,7 @@ function planSkillOperations(input) {
|
|
|
181197
182302
|
if (!availableSkills.has(skillName)) {
|
|
181198
182303
|
continue;
|
|
181199
182304
|
}
|
|
181200
|
-
const destinationDir =
|
|
182305
|
+
const destinationDir = join12(input.projectRoot, destination, skillName);
|
|
181201
182306
|
const path = relative3(input.projectRoot, destinationDir);
|
|
181202
182307
|
const kind = classifyManagedPath(input.projectRoot, path);
|
|
181203
182308
|
if (kind !== "absent" && kind !== "directory") {
|
|
@@ -181236,7 +182341,7 @@ function planLegacySkillOperations(input) {
|
|
|
181236
182341
|
const operations = [];
|
|
181237
182342
|
const leftover = [];
|
|
181238
182343
|
for (const skillName of LEGACY_SKILL_NAMES) {
|
|
181239
|
-
const destinationDir =
|
|
182344
|
+
const destinationDir = join12(input.projectRoot, input.destination, skillName);
|
|
181240
182345
|
const path = relative3(input.projectRoot, destinationDir);
|
|
181241
182346
|
const kind = classifyManagedPath(input.projectRoot, path);
|
|
181242
182347
|
if (kind === "absent") {
|
|
@@ -181316,7 +182421,7 @@ function resolveInitWithAgentTarget(discovery, options) {
|
|
|
181316
182421
|
return { target: discovery.agent.defaultTarget, error: undefined };
|
|
181317
182422
|
}
|
|
181318
182423
|
function readExistingConfig(projectRoot) {
|
|
181319
|
-
const configPath =
|
|
182424
|
+
const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
|
|
181320
182425
|
let rawText;
|
|
181321
182426
|
try {
|
|
181322
182427
|
rawText = readFileSync7(configPath, "utf8");
|
|
@@ -181567,7 +182672,7 @@ function executePlan(projectRoot, plan, packageRoot) {
|
|
|
181567
182672
|
}
|
|
181568
182673
|
}
|
|
181569
182674
|
function executeFileOperation(projectRoot, operation) {
|
|
181570
|
-
const absolutePath =
|
|
182675
|
+
const absolutePath = join13(projectRoot, operation.path);
|
|
181571
182676
|
if (operation.action === "create" && operation.content !== undefined) {
|
|
181572
182677
|
writeFileSync2(absolutePath, operation.content, "utf8");
|
|
181573
182678
|
}
|
|
@@ -181576,10 +182681,10 @@ function executeFileOperation(projectRoot, operation) {
|
|
|
181576
182681
|
}
|
|
181577
182682
|
}
|
|
181578
182683
|
function resolveProjectRoot(root, command) {
|
|
181579
|
-
const projectRoot =
|
|
182684
|
+
const projectRoot = resolve3(root);
|
|
181580
182685
|
let stats;
|
|
181581
182686
|
try {
|
|
181582
|
-
stats =
|
|
182687
|
+
stats = statSync4(projectRoot);
|
|
181583
182688
|
} catch {
|
|
181584
182689
|
throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
|
|
181585
182690
|
}
|
|
@@ -181614,24 +182719,12 @@ ${block.content}`;
|
|
|
181614
182719
|
const fence = codeFence(block.content);
|
|
181615
182720
|
return `${header}
|
|
181616
182721
|
|
|
181617
|
-
${fence}${fenceLanguage(block
|
|
182722
|
+
${fence}${fenceLanguage(block)}
|
|
181618
182723
|
${block.content}
|
|
181619
182724
|
${fence}`;
|
|
181620
182725
|
}
|
|
181621
|
-
function fenceLanguage(
|
|
181622
|
-
|
|
181623
|
-
return "swift";
|
|
181624
|
-
}
|
|
181625
|
-
if (language === "dart") {
|
|
181626
|
-
return "dart";
|
|
181627
|
-
}
|
|
181628
|
-
if (language === "rust") {
|
|
181629
|
-
return "rust";
|
|
181630
|
-
}
|
|
181631
|
-
if (language === "go") {
|
|
181632
|
-
return "go";
|
|
181633
|
-
}
|
|
181634
|
-
return "ts";
|
|
182726
|
+
function fenceLanguage(block) {
|
|
182727
|
+
return block.language === undefined ? "ts" : codeFenceLanguage(block.language, block.filePath);
|
|
181635
182728
|
}
|
|
181636
182729
|
function codeFence(content) {
|
|
181637
182730
|
let longestRun = 0;
|
|
@@ -181763,12 +182856,12 @@ function formatRelatedSummary(summary) {
|
|
|
181763
182856
|
}
|
|
181764
182857
|
|
|
181765
182858
|
// src/cli/upgrade.ts
|
|
181766
|
-
import { statSync as
|
|
181767
|
-
import { resolve as
|
|
182859
|
+
import { statSync as statSync5 } from "node:fs";
|
|
182860
|
+
import { resolve as resolve4 } from "node:path";
|
|
181768
182861
|
|
|
181769
182862
|
// src/setup/upgrade-plan.ts
|
|
181770
|
-
import { existsSync as
|
|
181771
|
-
import { join as
|
|
182863
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
182864
|
+
import { join as join14, relative as relative4 } from "node:path";
|
|
181772
182865
|
var MANAGED_SKILL_NAME = INIT_SKILL_NAMES[0];
|
|
181773
182866
|
function reportCliVersion(currentVersion, latest) {
|
|
181774
182867
|
if (latest.status !== "ok") {
|
|
@@ -181786,7 +182879,7 @@ function resolveUpgradeAgentTarget(discovery, explicitTarget) {
|
|
|
181786
182879
|
return explicitTarget ?? discovery.agent.defaultTarget;
|
|
181787
182880
|
}
|
|
181788
182881
|
function inspectManagedSkill(input) {
|
|
181789
|
-
const destinationDir =
|
|
182882
|
+
const destinationDir = join14(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
|
|
181790
182883
|
const path = toRootRelative(input.projectRoot, destinationDir);
|
|
181791
182884
|
const empty = {
|
|
181792
182885
|
destination: input.destination,
|
|
@@ -181802,7 +182895,7 @@ function inspectManagedSkill(input) {
|
|
|
181802
182895
|
if (!input.templateAvailable) {
|
|
181803
182896
|
return { ...empty, state: "template-missing" };
|
|
181804
182897
|
}
|
|
181805
|
-
const comparison = compareSkillTree(destinationDir,
|
|
182898
|
+
const comparison = compareSkillTree(destinationDir, join14(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
|
|
181806
182899
|
const drifted = comparison.modified.length > 0 || comparison.missing.length > 0 || comparison.extra.length > 0;
|
|
181807
182900
|
return {
|
|
181808
182901
|
destination: input.destination,
|
|
@@ -181816,13 +182909,13 @@ function inspectManagedSkill(input) {
|
|
|
181816
182909
|
function inspectLegacySkills(projectRoot, destination) {
|
|
181817
182910
|
const reports = [];
|
|
181818
182911
|
for (const skillName of LEGACY_SKILL_NAMES) {
|
|
181819
|
-
const destinationDir =
|
|
182912
|
+
const destinationDir = join14(projectRoot, destination, skillName);
|
|
181820
182913
|
const path = toRootRelative(projectRoot, destinationDir);
|
|
181821
182914
|
const kind = classifyManagedPath(projectRoot, path);
|
|
181822
182915
|
if (kind === "absent") {
|
|
181823
182916
|
continue;
|
|
181824
182917
|
}
|
|
181825
|
-
if (kind === "symlinked-parent" && !
|
|
182918
|
+
if (kind === "symlinked-parent" && !existsSync6(destinationDir)) {
|
|
181826
182919
|
continue;
|
|
181827
182920
|
}
|
|
181828
182921
|
reports.push({ path, kind });
|
|
@@ -182086,10 +183179,10 @@ function parseAgentTarget(value) {
|
|
|
182086
183179
|
return value;
|
|
182087
183180
|
}
|
|
182088
183181
|
function resolveProjectRoot2(root) {
|
|
182089
|
-
const projectRoot =
|
|
183182
|
+
const projectRoot = resolve4(root);
|
|
182090
183183
|
let stats;
|
|
182091
183184
|
try {
|
|
182092
|
-
stats =
|
|
183185
|
+
stats = statSync5(projectRoot);
|
|
182093
183186
|
} catch {
|
|
182094
183187
|
throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance("upgrade"));
|
|
182095
183188
|
}
|
|
@@ -182114,10 +183207,10 @@ function parseGraphOptions(args) {
|
|
|
182114
183207
|
return parseCommandOptions("graph", args);
|
|
182115
183208
|
}
|
|
182116
183209
|
function resolveProjectRoot3(root, command) {
|
|
182117
|
-
const projectRoot =
|
|
183210
|
+
const projectRoot = resolve5(root);
|
|
182118
183211
|
let stats;
|
|
182119
183212
|
try {
|
|
182120
|
-
stats =
|
|
183213
|
+
stats = statSync6(projectRoot);
|
|
182121
183214
|
} catch {
|
|
182122
183215
|
throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
|
|
182123
183216
|
}
|
|
@@ -182241,7 +183334,7 @@ function repairGuidanceFor(diagnostics, projectRoot) {
|
|
|
182241
183334
|
if (invalid.length === 0) {
|
|
182242
183335
|
return;
|
|
182243
183336
|
}
|
|
182244
|
-
return
|
|
183337
|
+
return existsSync7(join15(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
|
|
182245
183338
|
}
|
|
182246
183339
|
function unknownCommandGuidance(command) {
|
|
182247
183340
|
const suggestion = nearestMatch(command, SUBCOMMANDS);
|
|
@@ -182316,7 +183409,7 @@ function run(argv, io = {
|
|
|
182316
183409
|
function rootOptionOf(argv, currentDirectory) {
|
|
182317
183410
|
const flagIndex = argv.indexOf("--root");
|
|
182318
183411
|
const value = flagIndex === -1 ? undefined : argv[flagIndex + 1];
|
|
182319
|
-
return value === undefined ? currentDirectory :
|
|
183412
|
+
return value === undefined ? currentDirectory : resolve5(currentDirectory, value);
|
|
182320
183413
|
}
|
|
182321
183414
|
function writeUpdateNotice(argv, io, initRuntime, cliRuntime) {
|
|
182322
183415
|
const latest = cliRuntime.latest;
|