docbridge 0.11.0 → 0.12.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 +56 -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 +1761 -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 +104 -17
- package/docs/user/getting-started.md +1 -1
- package/docs/user/linking.md +79 -10
- package/docs/user/troubleshooting.md +22 -9
- package/package.json +1 -1
- package/schemas/common-output.schema.json +11 -1
- package/schemas/context-output.schema.json +1 -1
- package/schemas/docbridge.schema.json +95 -2
- package/schemas/graph-output.schema.json +1 -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.12.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,46 @@ var KNOWN_CODE_LANGUAGES = [
|
|
|
175788
175778
|
"swift",
|
|
175789
175779
|
"dart",
|
|
175790
175780
|
"rust",
|
|
175791
|
-
"go"
|
|
175781
|
+
"go",
|
|
175782
|
+
"javascript",
|
|
175783
|
+
"python",
|
|
175784
|
+
"ruby"
|
|
175792
175785
|
];
|
|
175793
175786
|
function isCodeLanguage(value) {
|
|
175794
175787
|
return KNOWN_CODE_LANGUAGES.includes(value);
|
|
175795
175788
|
}
|
|
175789
|
+
var LANGUAGE_SUFFIXES = {
|
|
175790
|
+
typescript: [".ts", ".tsx", ".mts", ".cts"],
|
|
175791
|
+
swift: [".swift"],
|
|
175792
|
+
dart: [".dart"],
|
|
175793
|
+
rust: [".rs"],
|
|
175794
|
+
go: [".go"],
|
|
175795
|
+
javascript: [".js", ".jsx", ".mjs", ".cjs"],
|
|
175796
|
+
python: [".py"],
|
|
175797
|
+
ruby: [".rb"]
|
|
175798
|
+
};
|
|
175799
|
+
var EXCLUDED_SUFFIXES = {
|
|
175800
|
+
typescript: [".d.ts", ".d.mts", ".d.cts"],
|
|
175801
|
+
swift: [],
|
|
175802
|
+
dart: [],
|
|
175803
|
+
rust: [],
|
|
175804
|
+
go: [],
|
|
175805
|
+
javascript: [],
|
|
175806
|
+
python: [],
|
|
175807
|
+
ruby: []
|
|
175808
|
+
};
|
|
175809
|
+
function codeFenceLanguage(language, filePath) {
|
|
175810
|
+
if (language === "typescript") {
|
|
175811
|
+
return filePath.endsWith(".tsx") ? "tsx" : "ts";
|
|
175812
|
+
}
|
|
175813
|
+
if (language === "javascript") {
|
|
175814
|
+
return filePath.endsWith(".jsx") ? "jsx" : "js";
|
|
175815
|
+
}
|
|
175816
|
+
return language;
|
|
175817
|
+
}
|
|
175818
|
+
function hasExcludedSuffix(language, relPath) {
|
|
175819
|
+
return EXCLUDED_SUFFIXES[language].some((suffix) => relPath.endsWith(suffix));
|
|
175820
|
+
}
|
|
175796
175821
|
function collectCodeFiles(projectRoot, codeInclude) {
|
|
175797
175822
|
const collected = [];
|
|
175798
175823
|
const seen = new Set;
|
|
@@ -175801,7 +175826,7 @@ function collectCodeFiles(projectRoot, codeInclude) {
|
|
|
175801
175826
|
if (entry === undefined) {
|
|
175802
175827
|
continue;
|
|
175803
175828
|
}
|
|
175804
|
-
for (const relPath of
|
|
175829
|
+
for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
|
|
175805
175830
|
if (seen.has(relPath)) {
|
|
175806
175831
|
continue;
|
|
175807
175832
|
}
|
|
@@ -175819,7 +175844,7 @@ function codeFileOwners(projectRoot, codeInclude) {
|
|
|
175819
175844
|
if (entry === undefined) {
|
|
175820
175845
|
continue;
|
|
175821
175846
|
}
|
|
175822
|
-
for (const relPath of
|
|
175847
|
+
for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
|
|
175823
175848
|
const existing = owners.get(relPath);
|
|
175824
175849
|
if (existing === undefined) {
|
|
175825
175850
|
owners.set(relPath, [language]);
|
|
@@ -175830,28 +175855,76 @@ function codeFileOwners(projectRoot, codeInclude) {
|
|
|
175830
175855
|
}
|
|
175831
175856
|
return owners;
|
|
175832
175857
|
}
|
|
175858
|
+
function collectLanguageFiles(projectRoot, language, patterns) {
|
|
175859
|
+
return collectFiles(projectRoot, patterns).filter((relPath) => !hasExcludedSuffix(language, relPath));
|
|
175860
|
+
}
|
|
175861
|
+
|
|
175862
|
+
// src/config/scanner-runtimes.ts
|
|
175863
|
+
var RUNTIME_WORKER_LANGUAGES = ["python", "ruby", "java"];
|
|
175864
|
+
var KNOWN_RUNTIME_ENTRY_KEYS = new Set(["command"]);
|
|
175865
|
+
function validateScannerRuntimes(value, diagnostics) {
|
|
175866
|
+
if (!isPlainObject(value)) {
|
|
175867
|
+
diagnostics.push(configDiagnostic("config_invalid_value", "scanners", "`scanners` must be an object keyed by runtime-backed language."));
|
|
175868
|
+
return {};
|
|
175869
|
+
}
|
|
175870
|
+
const result = {};
|
|
175871
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
175872
|
+
if (!isRuntimeWorkerLanguage(key)) {
|
|
175873
|
+
diagnostics.push(configDiagnostic("config_unknown_key", `scanners.${key}`, `Unknown scanner runtime: ${key}. Supported runtimes: ${RUNTIME_WORKER_LANGUAGES.join(", ")}.`));
|
|
175874
|
+
continue;
|
|
175875
|
+
}
|
|
175876
|
+
const command = validateRuntimeEntry(key, entry, diagnostics);
|
|
175877
|
+
if (command !== undefined) {
|
|
175878
|
+
result[key] = { command };
|
|
175879
|
+
}
|
|
175880
|
+
}
|
|
175881
|
+
return result;
|
|
175882
|
+
}
|
|
175883
|
+
function validateRuntimeEntry(language, value, diagnostics) {
|
|
175884
|
+
const target = `scanners.${language}`;
|
|
175885
|
+
if (!isPlainObject(value)) {
|
|
175886
|
+
diagnostics.push(configDiagnostic("config_invalid_value", target, `\`${target}\` must be an object with a \`command\` array.`));
|
|
175887
|
+
return;
|
|
175888
|
+
}
|
|
175889
|
+
for (const key of Object.keys(value)) {
|
|
175890
|
+
if (!KNOWN_RUNTIME_ENTRY_KEYS.has(key)) {
|
|
175891
|
+
diagnostics.push(configDiagnostic("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
|
|
175892
|
+
}
|
|
175893
|
+
}
|
|
175894
|
+
const command = value.command;
|
|
175895
|
+
if (!Array.isArray(command) || command.length === 0 || !command.every((part) => typeof part === "string" && part !== "")) {
|
|
175896
|
+
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.`));
|
|
175897
|
+
return;
|
|
175898
|
+
}
|
|
175899
|
+
return command;
|
|
175900
|
+
}
|
|
175901
|
+
function isRuntimeWorkerLanguage(value) {
|
|
175902
|
+
return RUNTIME_WORKER_LANGUAGES.includes(value);
|
|
175903
|
+
}
|
|
175904
|
+
function configDiagnostic(code, target, message) {
|
|
175905
|
+
return { severity: "error", code, target, message };
|
|
175906
|
+
}
|
|
175907
|
+
function isPlainObject(value) {
|
|
175908
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
175909
|
+
}
|
|
175833
175910
|
|
|
175834
175911
|
// src/config/config.ts
|
|
175835
175912
|
var CONFIG_FILE_NAME = "docbridge.config.json";
|
|
175836
175913
|
var EMPTY_CONFIG = {
|
|
175837
175914
|
include: { code: {}, docs: [] }
|
|
175838
175915
|
};
|
|
175839
|
-
var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
|
|
175916
|
+
var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include", "scanners"]);
|
|
175840
175917
|
var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
|
|
175841
175918
|
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
175919
|
var LANGUAGE_VISIBILITY = {
|
|
175850
175920
|
typescript: ["public", "protected", "private"],
|
|
175851
175921
|
swift: ["public", "open", "internal"],
|
|
175852
175922
|
dart: ["public"],
|
|
175853
175923
|
rust: ["pub", "private"],
|
|
175854
|
-
go: ["exported", "unexported"]
|
|
175924
|
+
go: ["exported", "unexported"],
|
|
175925
|
+
javascript: ["public", "protected", "private"],
|
|
175926
|
+
python: ["public", "private"],
|
|
175927
|
+
ruby: ["public", "protected", "private"]
|
|
175855
175928
|
};
|
|
175856
175929
|
function loadConfig(projectRoot) {
|
|
175857
175930
|
let rawText;
|
|
@@ -175880,7 +175953,7 @@ function resolveConfig(rawText) {
|
|
|
175880
175953
|
config: EMPTY_CONFIG,
|
|
175881
175954
|
ok: false,
|
|
175882
175955
|
diagnostics: [
|
|
175883
|
-
|
|
175956
|
+
configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
|
|
175884
175957
|
]
|
|
175885
175958
|
};
|
|
175886
175959
|
}
|
|
@@ -175893,57 +175966,61 @@ function resolveConfig(rawText) {
|
|
|
175893
175966
|
config: EMPTY_CONFIG,
|
|
175894
175967
|
ok: false,
|
|
175895
175968
|
diagnostics: [
|
|
175896
|
-
|
|
175969
|
+
configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
|
|
175897
175970
|
]
|
|
175898
175971
|
};
|
|
175899
175972
|
}
|
|
175900
175973
|
const diagnostics = [];
|
|
175901
|
-
if (!
|
|
175902
|
-
diagnostics.push(
|
|
175974
|
+
if (!isPlainObject2(parsed)) {
|
|
175975
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
|
|
175903
175976
|
return { config: EMPTY_CONFIG, diagnostics, ok: false };
|
|
175904
175977
|
}
|
|
175905
175978
|
for (const key of Object.keys(parsed)) {
|
|
175906
175979
|
if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
|
|
175907
|
-
diagnostics.push(
|
|
175980
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
|
|
175908
175981
|
}
|
|
175909
175982
|
}
|
|
175910
175983
|
if ("$schema" in parsed && typeof parsed.$schema !== "string") {
|
|
175911
|
-
diagnostics.push(
|
|
175984
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "$schema", "`$schema` must be a string."));
|
|
175912
175985
|
}
|
|
175986
|
+
const scanners = "scanners" in parsed ? validateScannerRuntimes(parsed.scanners, diagnostics) : undefined;
|
|
175913
175987
|
const include = parsed.include;
|
|
175914
|
-
if (!
|
|
175915
|
-
diagnostics.push(
|
|
175988
|
+
if (!isPlainObject2(include)) {
|
|
175989
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
|
|
175916
175990
|
return { config: EMPTY_CONFIG, diagnostics, ok: false };
|
|
175917
175991
|
}
|
|
175918
175992
|
for (const key of Object.keys(include)) {
|
|
175919
175993
|
if (!KNOWN_INCLUDE_KEYS.has(key)) {
|
|
175920
|
-
diagnostics.push(
|
|
175994
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
|
|
175921
175995
|
}
|
|
175922
175996
|
}
|
|
175923
175997
|
const code = validateCodeInclude(include.code, diagnostics);
|
|
175924
|
-
validatePatternArray(include.docs, "docs", ".md",
|
|
175998
|
+
validatePatternArray(include.docs, "docs", [".md"], [], diagnostics);
|
|
175925
175999
|
const ok = diagnostics.length === 0;
|
|
175926
|
-
const config = ok ? {
|
|
176000
|
+
const config = ok ? {
|
|
176001
|
+
include: { code, docs: include.docs },
|
|
176002
|
+
...scanners === undefined ? {} : { scanners }
|
|
176003
|
+
} : EMPTY_CONFIG;
|
|
175927
176004
|
return { config, diagnostics, ok };
|
|
175928
176005
|
}
|
|
175929
176006
|
function validateCodeInclude(value, diagnostics) {
|
|
175930
176007
|
if (Array.isArray(value)) {
|
|
175931
|
-
diagnostics.push(
|
|
176008
|
+
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
176009
|
return {};
|
|
175933
176010
|
}
|
|
175934
|
-
if (!
|
|
175935
|
-
diagnostics.push(
|
|
176011
|
+
if (!isPlainObject2(value)) {
|
|
176012
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
|
|
175936
176013
|
return {};
|
|
175937
176014
|
}
|
|
175938
176015
|
const keys = Object.keys(value);
|
|
175939
176016
|
if (keys.length === 0) {
|
|
175940
|
-
diagnostics.push(
|
|
176017
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
|
|
175941
176018
|
return {};
|
|
175942
176019
|
}
|
|
175943
176020
|
const result = {};
|
|
175944
176021
|
for (const key of keys) {
|
|
175945
176022
|
if (!isCodeLanguage(key)) {
|
|
175946
|
-
diagnostics.push(
|
|
176023
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
|
|
175947
176024
|
continue;
|
|
175948
176025
|
}
|
|
175949
176026
|
const entry = validateCodeEntry(key, value[key], diagnostics);
|
|
@@ -175955,20 +176032,20 @@ function validateCodeInclude(value, diagnostics) {
|
|
|
175955
176032
|
}
|
|
175956
176033
|
function validateCodeEntry(language, value, diagnostics) {
|
|
175957
176034
|
const target = `include.code.${language}`;
|
|
175958
|
-
if (!
|
|
175959
|
-
diagnostics.push(
|
|
176035
|
+
if (!isPlainObject2(value)) {
|
|
176036
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be an object with a \`patterns\` array. Shorthand arrays are not supported.`));
|
|
175960
176037
|
return;
|
|
175961
176038
|
}
|
|
175962
176039
|
for (const key of Object.keys(value)) {
|
|
175963
176040
|
if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
|
|
175964
|
-
diagnostics.push(
|
|
176041
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
|
|
175965
176042
|
}
|
|
175966
176043
|
}
|
|
175967
176044
|
const before = diagnostics.length;
|
|
175968
|
-
validatePatternArray(value.patterns, `code.${language}.patterns`,
|
|
176045
|
+
validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIXES[language], EXCLUDED_SUFFIXES[language], diagnostics);
|
|
175969
176046
|
if ("visibility" in value) {
|
|
175970
176047
|
if (!isStringArray(value.visibility)) {
|
|
175971
|
-
diagnostics.push(
|
|
176048
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
|
|
175972
176049
|
} else {
|
|
175973
176050
|
validateVisibilityOptions(language, value.visibility, target, diagnostics);
|
|
175974
176051
|
}
|
|
@@ -175985,47 +176062,49 @@ function validateCodeEntry(language, value, diagnostics) {
|
|
|
175985
176062
|
function validateVisibilityOptions(language, values, target, diagnostics) {
|
|
175986
176063
|
const allowed = LANGUAGE_VISIBILITY[language];
|
|
175987
176064
|
if (allowed.length === 0) {
|
|
175988
|
-
diagnostics.push(
|
|
176065
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
|
|
175989
176066
|
return;
|
|
175990
176067
|
}
|
|
175991
176068
|
for (const value of values) {
|
|
175992
176069
|
if (!allowed.includes(value)) {
|
|
175993
|
-
diagnostics.push(
|
|
176070
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
|
|
175994
176071
|
}
|
|
175995
176072
|
}
|
|
175996
176073
|
}
|
|
175997
|
-
function validatePatternArray(value, field,
|
|
176074
|
+
function validatePatternArray(value, field, suffixes, excludedSuffixes, diagnostics) {
|
|
175998
176075
|
const target = `include.${field}`;
|
|
175999
176076
|
if (!Array.isArray(value)) {
|
|
176000
|
-
diagnostics.push(
|
|
176077
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
|
|
176001
176078
|
return;
|
|
176002
176079
|
}
|
|
176003
176080
|
if (value.length === 0) {
|
|
176004
|
-
diagnostics.push(
|
|
176081
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
|
|
176005
176082
|
return;
|
|
176006
176083
|
}
|
|
176007
176084
|
for (const pattern of value) {
|
|
176008
176085
|
if (typeof pattern !== "string") {
|
|
176009
|
-
diagnostics.push(
|
|
176086
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
|
|
176010
176087
|
continue;
|
|
176011
176088
|
}
|
|
176012
176089
|
const globResult = validateGlobPattern(pattern);
|
|
176013
176090
|
if (!globResult.ok) {
|
|
176014
|
-
diagnostics.push(
|
|
176091
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
|
|
176015
176092
|
continue;
|
|
176016
176093
|
}
|
|
176017
|
-
const suffixError =
|
|
176094
|
+
const suffixError = checkPatternSuffix(pattern, suffixes, excludedSuffixes);
|
|
176018
176095
|
if (suffixError !== undefined) {
|
|
176019
|
-
diagnostics.push(
|
|
176096
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", pattern, suffixError));
|
|
176020
176097
|
}
|
|
176021
176098
|
}
|
|
176022
176099
|
}
|
|
176023
|
-
function
|
|
176024
|
-
if (!pattern.endsWith(
|
|
176025
|
-
|
|
176100
|
+
function checkPatternSuffix(pattern, suffixes, excludedSuffixes) {
|
|
176101
|
+
if (!suffixes.some((suffix) => pattern.endsWith(suffix))) {
|
|
176102
|
+
const listed = suffixes.map((suffix) => `\`${suffix}\``).join(", ");
|
|
176103
|
+
return suffixes.length === 1 ? `Pattern must end with ${listed}.` : `Pattern must end with one of ${listed}.`;
|
|
176026
176104
|
}
|
|
176027
|
-
|
|
176028
|
-
|
|
176105
|
+
const excluded = excludedSuffixes.find((suffix) => pattern.endsWith(suffix));
|
|
176106
|
+
if (excluded !== undefined) {
|
|
176107
|
+
return `Pattern must not target \`${excluded}\` declaration files.`;
|
|
176029
176108
|
}
|
|
176030
176109
|
return;
|
|
176031
176110
|
}
|
|
@@ -176033,15 +176112,15 @@ function detectLanguageOverlap(projectRoot, code) {
|
|
|
176033
176112
|
const diagnostics = [];
|
|
176034
176113
|
for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
|
|
176035
176114
|
if (owners.length > 1) {
|
|
176036
|
-
diagnostics.push(
|
|
176115
|
+
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
176116
|
}
|
|
176038
176117
|
}
|
|
176039
176118
|
return diagnostics;
|
|
176040
176119
|
}
|
|
176041
|
-
function
|
|
176120
|
+
function configDiagnostic2(code, target, message) {
|
|
176042
176121
|
return { severity: "error", code, target, message };
|
|
176043
176122
|
}
|
|
176044
|
-
function
|
|
176123
|
+
function isPlainObject2(value) {
|
|
176045
176124
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
176046
176125
|
}
|
|
176047
176126
|
function isStringArray(value) {
|
|
@@ -176568,6 +176647,11 @@ function positionOf(range) {
|
|
|
176568
176647
|
// src/lsp/server.ts
|
|
176569
176648
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
176570
176649
|
|
|
176650
|
+
// src/shared/error.ts
|
|
176651
|
+
function reasonOf(error) {
|
|
176652
|
+
return error instanceof Error ? error.message : String(error);
|
|
176653
|
+
}
|
|
176654
|
+
|
|
176571
176655
|
// src/lsp/position.ts
|
|
176572
176656
|
function toLspPosition(position) {
|
|
176573
176657
|
return { line: position.line - 1, character: position.column - 1 };
|
|
@@ -176951,32 +177035,27 @@ function renderCodeSignatures(state, counterparts) {
|
|
|
176951
177035
|
const signature = content === undefined || symbol.signatureRange === undefined ? "" : withoutLeadingComments(sliceSourceRange(content, symbol.signatureRange).content, symbol.language).trimEnd();
|
|
176952
177036
|
const fenced = signature.length > 0 ? `
|
|
176953
177037
|
|
|
176954
|
-
\`\`\`${
|
|
177038
|
+
\`\`\`${codeFenceLanguage(symbol.language, symbol.filePath)}
|
|
176955
177039
|
${signature}
|
|
176956
177040
|
\`\`\`` : "";
|
|
176957
177041
|
blocks.push(`**${symbol.endpoint}**${fenced}`);
|
|
176958
177042
|
}
|
|
176959
177043
|
return blocks.length > 0 ? blocks.join(DIVIDER) : null;
|
|
176960
177044
|
}
|
|
176961
|
-
var FENCE_LANGUAGE = {
|
|
176962
|
-
typescript: "ts",
|
|
176963
|
-
swift: "swift",
|
|
176964
|
-
dart: "dart",
|
|
176965
|
-
rust: "rust",
|
|
176966
|
-
go: "go"
|
|
176967
|
-
};
|
|
176968
177045
|
var NESTED_BLOCK_COMMENTS = new Set(["swift", "dart", "rust"]);
|
|
177046
|
+
var HASH_LINE_COMMENTS = new Set(["python", "ruby"]);
|
|
176969
177047
|
function withoutLeadingComments(text, language) {
|
|
177048
|
+
const lineComment = HASH_LINE_COMMENTS.has(language) ? "#" : "//";
|
|
176970
177049
|
let index = 0;
|
|
176971
177050
|
for (;; ) {
|
|
176972
177051
|
while (index < text.length && /\s/.test(text[index] ?? "")) {
|
|
176973
177052
|
index += 1;
|
|
176974
177053
|
}
|
|
176975
|
-
if (text.startsWith(
|
|
177054
|
+
if (text.startsWith(lineComment, index)) {
|
|
176976
177055
|
const newline = text.indexOf(`
|
|
176977
177056
|
`, index);
|
|
176978
177057
|
index = newline === -1 ? text.length : newline + 1;
|
|
176979
|
-
} else if (text.startsWith("/*", index)) {
|
|
177058
|
+
} else if (lineComment === "//" && text.startsWith("/*", index)) {
|
|
176980
177059
|
index = blockCommentEnd(text, index, NESTED_BLOCK_COMMENTS.has(language));
|
|
176981
177060
|
} else {
|
|
176982
177061
|
return text.slice(index);
|
|
@@ -177517,24 +177596,205 @@ function manifestLink(source, target, entry, side) {
|
|
|
177517
177596
|
};
|
|
177518
177597
|
}
|
|
177519
177598
|
|
|
177599
|
+
// src/shared/cancelable.ts
|
|
177600
|
+
function deferred() {
|
|
177601
|
+
let settle;
|
|
177602
|
+
const promise = new Promise((resolve2, reject) => {
|
|
177603
|
+
settle = { resolve: resolve2, reject };
|
|
177604
|
+
});
|
|
177605
|
+
return {
|
|
177606
|
+
promise,
|
|
177607
|
+
resolve: (value) => settle?.resolve(value),
|
|
177608
|
+
reject: (error) => settle?.reject(error)
|
|
177609
|
+
};
|
|
177610
|
+
}
|
|
177611
|
+
function settledCancelable(value) {
|
|
177612
|
+
return { promise: Promise.resolve(value), cancel: () => {
|
|
177613
|
+
return;
|
|
177614
|
+
} };
|
|
177615
|
+
}
|
|
177616
|
+
function mapCancelable(task, map) {
|
|
177617
|
+
const mapped = deferred();
|
|
177618
|
+
let cancelled = false;
|
|
177619
|
+
task.promise.then((value) => {
|
|
177620
|
+
if (cancelled) {
|
|
177621
|
+
throw abortError();
|
|
177622
|
+
}
|
|
177623
|
+
return map(value);
|
|
177624
|
+
}).then(mapped.resolve, mapped.reject);
|
|
177625
|
+
return {
|
|
177626
|
+
promise: mapped.promise,
|
|
177627
|
+
cancel() {
|
|
177628
|
+
cancelled = true;
|
|
177629
|
+
mapped.reject(abortError());
|
|
177630
|
+
task.cancel();
|
|
177631
|
+
}
|
|
177632
|
+
};
|
|
177633
|
+
}
|
|
177634
|
+
function abortError() {
|
|
177635
|
+
const error = new Error("The operation was cancelled");
|
|
177636
|
+
error.name = "AbortError";
|
|
177637
|
+
return error;
|
|
177638
|
+
}
|
|
177639
|
+
function isAbortError(error) {
|
|
177640
|
+
return error instanceof Error && error.name === "AbortError";
|
|
177641
|
+
}
|
|
177642
|
+
function cancelableSequence(body) {
|
|
177643
|
+
const outcome = deferred();
|
|
177644
|
+
let cancelled = false;
|
|
177645
|
+
let running;
|
|
177646
|
+
const step = async (next) => {
|
|
177647
|
+
if (cancelled) {
|
|
177648
|
+
if (typeof next !== "function") {
|
|
177649
|
+
next.promise.catch(() => {
|
|
177650
|
+
return;
|
|
177651
|
+
});
|
|
177652
|
+
next.cancel();
|
|
177653
|
+
}
|
|
177654
|
+
throw abortError();
|
|
177655
|
+
}
|
|
177656
|
+
const task = typeof next === "function" ? next() : next;
|
|
177657
|
+
running = task;
|
|
177658
|
+
try {
|
|
177659
|
+
return await task.promise;
|
|
177660
|
+
} finally {
|
|
177661
|
+
if (running === task) {
|
|
177662
|
+
running = undefined;
|
|
177663
|
+
}
|
|
177664
|
+
}
|
|
177665
|
+
};
|
|
177666
|
+
(async () => body(step))().then(outcome.resolve, outcome.reject);
|
|
177667
|
+
return {
|
|
177668
|
+
promise: outcome.promise,
|
|
177669
|
+
cancel() {
|
|
177670
|
+
if (cancelled) {
|
|
177671
|
+
return;
|
|
177672
|
+
}
|
|
177673
|
+
cancelled = true;
|
|
177674
|
+
outcome.reject(abortError());
|
|
177675
|
+
running?.cancel();
|
|
177676
|
+
}
|
|
177677
|
+
};
|
|
177678
|
+
}
|
|
177679
|
+
|
|
177680
|
+
// src/scan/code/scan-cache.ts
|
|
177681
|
+
import { createHash } from "node:crypto";
|
|
177682
|
+
function emptyCodeScanCache() {
|
|
177683
|
+
return { fingerprint: "", entries: new Map };
|
|
177684
|
+
}
|
|
177685
|
+
function codeScanCacheKey(language, filePath, content, options, argv, runtime = []) {
|
|
177686
|
+
const contentHash = createHash("sha256").update(content).digest("hex");
|
|
177687
|
+
return JSON.stringify([
|
|
177688
|
+
language,
|
|
177689
|
+
filePath,
|
|
177690
|
+
contentHash,
|
|
177691
|
+
options.visibility ?? null,
|
|
177692
|
+
argv,
|
|
177693
|
+
runtime
|
|
177694
|
+
]);
|
|
177695
|
+
}
|
|
177696
|
+
function isReusableScan(scan) {
|
|
177697
|
+
return !scan.diagnostics.some((diagnostic) => diagnostic.code === "code_scanner_unavailable" || diagnostic.code === "code_scanner_failed");
|
|
177698
|
+
}
|
|
177699
|
+
|
|
177520
177700
|
// src/scan/code/typescript.ts
|
|
177701
|
+
var import_typescript2 = __toESM(require_typescript(), 1);
|
|
177702
|
+
|
|
177703
|
+
// src/scan/code/typescript-diagnostics.ts
|
|
177521
177704
|
var import_typescript = __toESM(require_typescript(), 1);
|
|
177522
|
-
|
|
177705
|
+
function scriptLanguage(filePath) {
|
|
177706
|
+
return LANGUAGE_SUFFIXES.javascript.some((suffix) => filePath.endsWith(suffix)) ? "javascript" : "typescript";
|
|
177707
|
+
}
|
|
177708
|
+
var LABEL = {
|
|
177709
|
+
typescript: "TypeScript",
|
|
177710
|
+
javascript: "JavaScript"
|
|
177711
|
+
};
|
|
177712
|
+
var SUPPORTED_DECLARATIONS = {
|
|
177713
|
+
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.",
|
|
177714
|
+
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."
|
|
177715
|
+
};
|
|
177716
|
+
function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
|
|
177717
|
+
const location = { filePath, line: 1, column: 1 };
|
|
177718
|
+
if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
|
|
177719
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
|
|
177720
|
+
location.line = line + 1;
|
|
177721
|
+
location.column = character + 1;
|
|
177722
|
+
}
|
|
177723
|
+
const language = scriptLanguage(filePath);
|
|
177724
|
+
const detail = diagnostic === undefined ? `${LABEL[language]} file has a syntactic parse error.` : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
177725
|
+
`);
|
|
177726
|
+
return {
|
|
177727
|
+
severity: "error",
|
|
177728
|
+
code: "code_parse_error",
|
|
177729
|
+
language,
|
|
177730
|
+
target: filePath,
|
|
177731
|
+
message: `${LABEL[language]} parse error: ${detail}`,
|
|
177732
|
+
location
|
|
177733
|
+
};
|
|
177734
|
+
}
|
|
177735
|
+
function unsupportedDeclarationDiagnostic(filePath, location) {
|
|
177736
|
+
const language = scriptLanguage(filePath);
|
|
177737
|
+
return {
|
|
177738
|
+
severity: "warning",
|
|
177739
|
+
code: "unsupported_declaration",
|
|
177740
|
+
language,
|
|
177741
|
+
target: filePath,
|
|
177742
|
+
message: `@doc is attached to an unsupported declaration. ${SUPPORTED_DECLARATIONS[language]}`,
|
|
177743
|
+
location
|
|
177744
|
+
};
|
|
177745
|
+
}
|
|
177746
|
+
function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
|
|
177747
|
+
const diagnostic = {
|
|
177748
|
+
severity: "error",
|
|
177749
|
+
code: "duplicate_code_symbol",
|
|
177750
|
+
language: scriptLanguage(location.filePath),
|
|
177751
|
+
target: endpoint,
|
|
177752
|
+
message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
|
|
177753
|
+
location
|
|
177754
|
+
};
|
|
177755
|
+
if (range !== undefined) {
|
|
177756
|
+
diagnostic.range = range;
|
|
177757
|
+
}
|
|
177758
|
+
return diagnostic;
|
|
177759
|
+
}
|
|
177760
|
+
function duplicateLinkDiagnostic(source, target, location, range) {
|
|
177761
|
+
const diagnostic = {
|
|
177762
|
+
severity: "warning",
|
|
177763
|
+
code: "duplicate_link",
|
|
177764
|
+
target,
|
|
177765
|
+
source,
|
|
177766
|
+
message: `Duplicate @doc link from ${source} to ${target}.`,
|
|
177767
|
+
location
|
|
177768
|
+
};
|
|
177769
|
+
if (range !== undefined) {
|
|
177770
|
+
diagnostic.range = range;
|
|
177771
|
+
}
|
|
177772
|
+
return diagnostic;
|
|
177773
|
+
}
|
|
177774
|
+
|
|
177775
|
+
// src/scan/code/typescript.ts
|
|
177523
177776
|
var typeScriptAdapter = {
|
|
177524
|
-
language:
|
|
177777
|
+
language: "typescript",
|
|
177525
177778
|
scanFile(filePath, content, options) {
|
|
177526
177779
|
return scanTypeScript(filePath, content, options);
|
|
177780
|
+
},
|
|
177781
|
+
scanFiles(files, options) {
|
|
177782
|
+
return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
|
|
177527
177783
|
}
|
|
177528
177784
|
};
|
|
177785
|
+
var javaScriptAdapter = {
|
|
177786
|
+
...typeScriptAdapter,
|
|
177787
|
+
language: "javascript"
|
|
177788
|
+
};
|
|
177529
177789
|
var DEFAULT_MEMBER_VISIBILITY = ["public", "protected"];
|
|
177530
177790
|
function scanTypeScript(filePath, content, options = {}) {
|
|
177531
177791
|
const visibility = new Set(options.visibility ?? DEFAULT_MEMBER_VISIBILITY);
|
|
177532
|
-
const sourceFile =
|
|
177533
|
-
const
|
|
177534
|
-
if (
|
|
177535
|
-
const first =
|
|
177792
|
+
const sourceFile = import_typescript2.default.createSourceFile(filePath, content, import_typescript2.default.ScriptTarget.Latest, true, scriptKindOf(filePath));
|
|
177793
|
+
const syntaxErrors = syntaxDiagnostics(sourceFile);
|
|
177794
|
+
if (syntaxErrors.length > 0) {
|
|
177795
|
+
const first = syntaxErrors[0];
|
|
177536
177796
|
return {
|
|
177537
|
-
language:
|
|
177797
|
+
language: scriptLanguage(filePath),
|
|
177538
177798
|
filePath,
|
|
177539
177799
|
symbols: [],
|
|
177540
177800
|
undocumentedSymbols: [],
|
|
@@ -177604,11 +177864,13 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177604
177864
|
}
|
|
177605
177865
|
const parsed = parseLinkTarget(docTag.rawTarget, parseOptions);
|
|
177606
177866
|
if (!parsed.ok) {
|
|
177607
|
-
diagnostics.push(parsed.diagnostic);
|
|
177867
|
+
diagnostics.push(linkDiagnostic(filePath, parsed.diagnostic));
|
|
177608
177868
|
continue;
|
|
177609
177869
|
}
|
|
177610
177870
|
if (linkTargetsSeen.has(docTag.rawTarget)) {
|
|
177611
|
-
|
|
177871
|
+
const { rawTarget, location, targetRange } = docTag;
|
|
177872
|
+
const duplicate = duplicateLinkDiagnostic(endpoint, rawTarget, location, targetRange);
|
|
177873
|
+
diagnostics.push(linkDiagnostic(filePath, duplicate));
|
|
177612
177874
|
continue;
|
|
177613
177875
|
}
|
|
177614
177876
|
linkTargetsSeen.add(docTag.rawTarget);
|
|
@@ -177624,7 +177886,7 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177624
177886
|
}
|
|
177625
177887
|
}
|
|
177626
177888
|
return {
|
|
177627
|
-
language:
|
|
177889
|
+
language: scriptLanguage(filePath),
|
|
177628
177890
|
filePath,
|
|
177629
177891
|
symbols,
|
|
177630
177892
|
undocumentedSymbols,
|
|
@@ -177632,15 +177894,60 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177632
177894
|
diagnostics
|
|
177633
177895
|
};
|
|
177634
177896
|
}
|
|
177897
|
+
function linkDiagnostic(filePath, diagnostic) {
|
|
177898
|
+
if (scriptLanguage(filePath) !== "javascript") {
|
|
177899
|
+
return diagnostic;
|
|
177900
|
+
}
|
|
177901
|
+
const { severity, code, ...rest } = diagnostic;
|
|
177902
|
+
return { severity, code, language: "javascript", ...rest };
|
|
177903
|
+
}
|
|
177904
|
+
function scriptKindOf(filePath) {
|
|
177905
|
+
if (scriptLanguage(filePath) === "javascript") {
|
|
177906
|
+
return filePath.endsWith(".jsx") ? import_typescript2.default.ScriptKind.JSX : import_typescript2.default.ScriptKind.JS;
|
|
177907
|
+
}
|
|
177908
|
+
return filePath.endsWith(".tsx") ? import_typescript2.default.ScriptKind.TSX : import_typescript2.default.ScriptKind.TS;
|
|
177909
|
+
}
|
|
177910
|
+
function syntaxDiagnostics(sourceFile) {
|
|
177911
|
+
const parseDiagnostics = getParseDiagnostics(sourceFile);
|
|
177912
|
+
if (parseDiagnostics.length > 0 || scriptLanguage(sourceFile.fileName) !== "javascript") {
|
|
177913
|
+
return parseDiagnostics;
|
|
177914
|
+
}
|
|
177915
|
+
return javaScriptGrammarDiagnostics(sourceFile);
|
|
177916
|
+
}
|
|
177635
177917
|
function getParseDiagnostics(sourceFile) {
|
|
177636
177918
|
const withDiagnostics = sourceFile;
|
|
177637
177919
|
return withDiagnostics.parseDiagnostics ?? [];
|
|
177638
177920
|
}
|
|
177921
|
+
function javaScriptGrammarDiagnostics(sourceFile) {
|
|
177922
|
+
const { fileName } = sourceFile;
|
|
177923
|
+
const host = {
|
|
177924
|
+
getSourceFile: (name) => name === fileName ? sourceFile : undefined,
|
|
177925
|
+
fileExists: (name) => name === fileName,
|
|
177926
|
+
readFile: () => {
|
|
177927
|
+
return;
|
|
177928
|
+
},
|
|
177929
|
+
writeFile: () => {
|
|
177930
|
+
return;
|
|
177931
|
+
},
|
|
177932
|
+
getDefaultLibFileName: () => "lib.d.ts",
|
|
177933
|
+
getCurrentDirectory: () => "/",
|
|
177934
|
+
getCanonicalFileName: (name) => name,
|
|
177935
|
+
useCaseSensitiveFileNames: () => true,
|
|
177936
|
+
getNewLine: () => `
|
|
177937
|
+
`
|
|
177938
|
+
};
|
|
177939
|
+
const program = import_typescript2.default.createProgram({
|
|
177940
|
+
rootNames: [fileName],
|
|
177941
|
+
options: { allowJs: true, noLib: true, noResolve: true, types: [] },
|
|
177942
|
+
host
|
|
177943
|
+
});
|
|
177944
|
+
return program.getSyntacticDiagnostics(sourceFile);
|
|
177945
|
+
}
|
|
177639
177946
|
function collectDocTags(filePath, sourceFile, statement) {
|
|
177640
177947
|
const node = jsDocCarrier(statement);
|
|
177641
177948
|
const location = locationOf(filePath, sourceFile, statement);
|
|
177642
177949
|
const tags = [];
|
|
177643
|
-
for (const tag of
|
|
177950
|
+
for (const tag of import_typescript2.default.getJSDocTags(node)) {
|
|
177644
177951
|
if (tag.tagName.escapedText !== "doc") {
|
|
177645
177952
|
continue;
|
|
177646
177953
|
}
|
|
@@ -177659,7 +177966,7 @@ function collectDocTags(filePath, sourceFile, statement) {
|
|
|
177659
177966
|
return tags;
|
|
177660
177967
|
}
|
|
177661
177968
|
function jsDocCarrier(statement) {
|
|
177662
|
-
if (
|
|
177969
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177663
177970
|
const declaration = statement.declarationList.declarations[0];
|
|
177664
177971
|
if (declaration !== undefined) {
|
|
177665
177972
|
return declaration;
|
|
@@ -177705,11 +178012,11 @@ function collectMemberDeclarations(filePath, sourceFile, statement, visibility,
|
|
|
177705
178012
|
}
|
|
177706
178013
|
}
|
|
177707
178014
|
function diagnoseParameterProperties(filePath, sourceFile, member, diagnostics) {
|
|
177708
|
-
if (!
|
|
178015
|
+
if (!import_typescript2.default.isConstructorDeclaration(member)) {
|
|
177709
178016
|
return;
|
|
177710
178017
|
}
|
|
177711
178018
|
for (const parameter of member.parameters) {
|
|
177712
|
-
if (!
|
|
178019
|
+
if (!import_typescript2.default.isParameterPropertyDeclaration(parameter, member)) {
|
|
177713
178020
|
continue;
|
|
177714
178021
|
}
|
|
177715
178022
|
if (collectDocTags(filePath, sourceFile, parameter).length > 0) {
|
|
@@ -177725,22 +178032,22 @@ function describeContainer(statement) {
|
|
|
177725
178032
|
return { name: supportedNameNode(statement)?.text ?? null, members };
|
|
177726
178033
|
}
|
|
177727
178034
|
function containerMembers(statement) {
|
|
177728
|
-
if (
|
|
178035
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177729
178036
|
return statement.members;
|
|
177730
178037
|
}
|
|
177731
|
-
if (
|
|
178038
|
+
if (import_typescript2.default.isInterfaceDeclaration(statement)) {
|
|
177732
178039
|
return statement.members;
|
|
177733
178040
|
}
|
|
177734
|
-
if (
|
|
178041
|
+
if (import_typescript2.default.isEnumDeclaration(statement)) {
|
|
177735
178042
|
return statement.members;
|
|
177736
178043
|
}
|
|
177737
|
-
if (
|
|
177738
|
-
return
|
|
178044
|
+
if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
|
|
178045
|
+
return import_typescript2.default.isTypeLiteralNode(statement.type) ? statement.type.members : null;
|
|
177739
178046
|
}
|
|
177740
|
-
if (
|
|
178047
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177741
178048
|
const declarations = statement.declarationList.declarations;
|
|
177742
178049
|
const initializer = declarations.length === 1 ? declarations[0]?.initializer : undefined;
|
|
177743
|
-
return initializer !== undefined &&
|
|
178050
|
+
return initializer !== undefined && import_typescript2.default.isClassExpression(initializer) ? initializer.members : null;
|
|
177744
178051
|
}
|
|
177745
178052
|
return null;
|
|
177746
178053
|
}
|
|
@@ -177762,24 +178069,24 @@ function describeMember(filePath, sourceFile, containerName, member, docTags) {
|
|
|
177762
178069
|
};
|
|
177763
178070
|
}
|
|
177764
178071
|
function isVisibleMember(member, visibility) {
|
|
177765
|
-
if (hasModifier(member,
|
|
178072
|
+
if (hasModifier(member, import_typescript2.default.SyntaxKind.PrivateKeyword)) {
|
|
177766
178073
|
return visibility.has("private");
|
|
177767
178074
|
}
|
|
177768
|
-
if (hasModifier(member,
|
|
178075
|
+
if (hasModifier(member, import_typescript2.default.SyntaxKind.ProtectedKeyword)) {
|
|
177769
178076
|
return visibility.has("protected");
|
|
177770
178077
|
}
|
|
177771
178078
|
return visibility.has("public");
|
|
177772
178079
|
}
|
|
177773
178080
|
function memberIdentity(sourceFile, member) {
|
|
177774
|
-
if (
|
|
177775
|
-
const keyword = member.getChildren(sourceFile).find((child) => child.kind ===
|
|
178081
|
+
if (import_typescript2.default.isConstructorDeclaration(member)) {
|
|
178082
|
+
const keyword = member.getChildren(sourceFile).find((child) => child.kind === import_typescript2.default.SyntaxKind.ConstructorKeyword);
|
|
177776
178083
|
return keyword === undefined ? null : { name: "constructor", nameRange: rangeOfNode(sourceFile, keyword) };
|
|
177777
178084
|
}
|
|
177778
|
-
if (!
|
|
178085
|
+
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
178086
|
return null;
|
|
177780
178087
|
}
|
|
177781
178088
|
const name = member.name;
|
|
177782
|
-
return
|
|
178089
|
+
return import_typescript2.default.isIdentifier(name) ? { name: name.text, nameRange: rangeOfNode(sourceFile, name) } : null;
|
|
177783
178090
|
}
|
|
177784
178091
|
function memberSignatureEndOffset(sourceFile, member) {
|
|
177785
178092
|
const body = member.body;
|
|
@@ -177788,28 +178095,28 @@ function memberSignatureEndOffset(sourceFile, member) {
|
|
|
177788
178095
|
function supportedNameNode(statement) {
|
|
177789
178096
|
const isExported = hasExportModifier(statement);
|
|
177790
178097
|
const isDefault = hasDefaultModifier(statement);
|
|
177791
|
-
if (
|
|
178098
|
+
if (import_typescript2.default.isFunctionDeclaration(statement)) {
|
|
177792
178099
|
if (!isExported || statement.name === undefined) {
|
|
177793
178100
|
return null;
|
|
177794
178101
|
}
|
|
177795
178102
|
return statement.name;
|
|
177796
178103
|
}
|
|
177797
|
-
if (
|
|
178104
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177798
178105
|
if (!isExported || statement.name === undefined) {
|
|
177799
178106
|
return null;
|
|
177800
178107
|
}
|
|
177801
178108
|
return statement.name;
|
|
177802
178109
|
}
|
|
177803
|
-
if (
|
|
178110
|
+
if (import_typescript2.default.isInterfaceDeclaration(statement)) {
|
|
177804
178111
|
return isExported && !isDefault ? statement.name : null;
|
|
177805
178112
|
}
|
|
177806
|
-
if (
|
|
178113
|
+
if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
|
|
177807
178114
|
return isExported && !isDefault ? statement.name : null;
|
|
177808
178115
|
}
|
|
177809
|
-
if (
|
|
178116
|
+
if (import_typescript2.default.isEnumDeclaration(statement)) {
|
|
177810
178117
|
return isExported && !isDefault ? statement.name : null;
|
|
177811
178118
|
}
|
|
177812
|
-
if (
|
|
178119
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177813
178120
|
if (!isExported) {
|
|
177814
178121
|
return null;
|
|
177815
178122
|
}
|
|
@@ -177819,23 +178126,23 @@ function supportedNameNode(statement) {
|
|
|
177819
178126
|
return null;
|
|
177820
178127
|
}
|
|
177821
178128
|
const name = declaration.name;
|
|
177822
|
-
return
|
|
178129
|
+
return import_typescript2.default.isIdentifier(name) ? name : null;
|
|
177823
178130
|
}
|
|
177824
178131
|
return null;
|
|
177825
178132
|
}
|
|
177826
178133
|
function hasExportModifier(statement) {
|
|
177827
|
-
return hasModifier(statement,
|
|
178134
|
+
return hasModifier(statement, import_typescript2.default.SyntaxKind.ExportKeyword);
|
|
177828
178135
|
}
|
|
177829
178136
|
function hasDefaultModifier(statement) {
|
|
177830
|
-
return hasModifier(statement,
|
|
178137
|
+
return hasModifier(statement, import_typescript2.default.SyntaxKind.DefaultKeyword);
|
|
177831
178138
|
}
|
|
177832
178139
|
function hasModifier(node, kind) {
|
|
177833
|
-
const modifiers =
|
|
178140
|
+
const modifiers = import_typescript2.default.canHaveModifiers(node) ? import_typescript2.default.getModifiers(node) : undefined;
|
|
177834
178141
|
return modifiers?.some((modifier) => modifier.kind === kind) ?? false;
|
|
177835
178142
|
}
|
|
177836
178143
|
function declarationLocation(filePath, sourceFile, node) {
|
|
177837
|
-
const carrier =
|
|
177838
|
-
const name = carrier === undefined ? undefined :
|
|
178144
|
+
const carrier = import_typescript2.default.isVariableStatement(node) ? node.declarationList.declarations[0] : node;
|
|
178145
|
+
const name = carrier === undefined ? undefined : import_typescript2.default.getNameOfDeclaration(carrier);
|
|
177839
178146
|
return locationOf(filePath, sourceFile, name ?? node);
|
|
177840
178147
|
}
|
|
177841
178148
|
function locationOf(filePath, sourceFile, node) {
|
|
@@ -177854,13 +178161,13 @@ function rangeOfNode(sourceFile, node) {
|
|
|
177854
178161
|
return rangeFromOffsets(sourceFile, node.getStart(sourceFile), node.getEnd());
|
|
177855
178162
|
}
|
|
177856
178163
|
function signatureEndOffset(sourceFile, statement) {
|
|
177857
|
-
if (
|
|
178164
|
+
if (import_typescript2.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
|
|
177858
178165
|
return statement.body.getStart(sourceFile);
|
|
177859
178166
|
}
|
|
177860
|
-
if (
|
|
178167
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177861
178168
|
return classBodyStartOffset(sourceFile, statement) ?? statement.getEnd();
|
|
177862
178169
|
}
|
|
177863
|
-
if (
|
|
178170
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177864
178171
|
return variableSignatureEndOffset(sourceFile, statement) ?? statement.getEnd();
|
|
177865
178172
|
}
|
|
177866
178173
|
return statement.getEnd();
|
|
@@ -177876,13 +178183,13 @@ function variableSignatureEndOffset(sourceFile, statement) {
|
|
|
177876
178183
|
if (initializer === undefined) {
|
|
177877
178184
|
return;
|
|
177878
178185
|
}
|
|
177879
|
-
if ((
|
|
178186
|
+
if ((import_typescript2.default.isArrowFunction(initializer) || import_typescript2.default.isFunctionExpression(initializer)) && import_typescript2.default.isBlock(initializer.body)) {
|
|
177880
178187
|
return initializer.body.getStart(sourceFile);
|
|
177881
178188
|
}
|
|
177882
|
-
if (
|
|
178189
|
+
if (import_typescript2.default.isObjectLiteralExpression(initializer)) {
|
|
177883
178190
|
return initializer.getStart(sourceFile);
|
|
177884
178191
|
}
|
|
177885
|
-
if (
|
|
178192
|
+
if (import_typescript2.default.isClassExpression(initializer)) {
|
|
177886
178193
|
return classBodyStartOffset(sourceFile, initializer);
|
|
177887
178194
|
}
|
|
177888
178195
|
return;
|
|
@@ -177897,7 +178204,7 @@ function targetRangeOf(sourceFile, tag, rawTarget) {
|
|
|
177897
178204
|
function makeCodeSymbol(filePath, endpoint, declaration) {
|
|
177898
178205
|
const symbol = {
|
|
177899
178206
|
kind: "code",
|
|
177900
|
-
language:
|
|
178207
|
+
language: scriptLanguage(filePath),
|
|
177901
178208
|
filePath,
|
|
177902
178209
|
symbolName: declaration.symbolName,
|
|
177903
178210
|
canonicalId: declaration.canonicalId,
|
|
@@ -177934,224 +178241,20 @@ function firstToken(text) {
|
|
|
177934
178241
|
}
|
|
177935
178242
|
return trimmed.split(/\s+/)[0];
|
|
177936
178243
|
}
|
|
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
178244
|
|
|
177994
|
-
// src/scan/code/worker/
|
|
177995
|
-
import { accessSync
|
|
177996
|
-
import { join as
|
|
178245
|
+
// src/scan/code/worker/runtime-worker.ts
|
|
178246
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
|
|
178247
|
+
import { extname, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
|
|
177997
178248
|
|
|
177998
|
-
// src/
|
|
177999
|
-
|
|
178000
|
-
return error instanceof Error ? error.message : String(error);
|
|
178001
|
-
}
|
|
178249
|
+
// src/scan/code/worker/runtime-probe.ts
|
|
178250
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
178002
178251
|
|
|
178003
|
-
// src/
|
|
178004
|
-
|
|
178252
|
+
// src/scan/code/worker/scanner-worker.ts
|
|
178253
|
+
var import__2020 = __toESM(require_2020(), 1);
|
|
178254
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
178255
|
+
import { mkdirSync } from "node:fs";
|
|
178256
|
+
import { tmpdir } from "node:os";
|
|
178005
178257
|
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
178258
|
// schemas/common-output.schema.json
|
|
178156
178259
|
var common_output_schema_default = {
|
|
178157
178260
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -178199,7 +178302,17 @@ var common_output_schema_default = {
|
|
|
178199
178302
|
target: { type: "string" },
|
|
178200
178303
|
language: {
|
|
178201
178304
|
type: "string",
|
|
178202
|
-
enum: [
|
|
178305
|
+
enum: [
|
|
178306
|
+
"typescript",
|
|
178307
|
+
"swift",
|
|
178308
|
+
"dart",
|
|
178309
|
+
"rust",
|
|
178310
|
+
"go",
|
|
178311
|
+
"javascript",
|
|
178312
|
+
"python",
|
|
178313
|
+
"ruby",
|
|
178314
|
+
"java"
|
|
178315
|
+
]
|
|
178203
178316
|
},
|
|
178204
178317
|
source: { type: "string" },
|
|
178205
178318
|
message: { type: "string" },
|
|
@@ -178219,7 +178332,7 @@ var scanner_worker_schema_default = {
|
|
|
178219
178332
|
$defs: {
|
|
178220
178333
|
language: {
|
|
178221
178334
|
type: "string",
|
|
178222
|
-
enum: ["typescript", "swift", "dart", "rust", "go"]
|
|
178335
|
+
enum: ["typescript", "swift", "dart", "rust", "go", "javascript", "python", "ruby", "java"]
|
|
178223
178336
|
},
|
|
178224
178337
|
codeSymbol: {
|
|
178225
178338
|
type: "object",
|
|
@@ -178335,6 +178448,8 @@ var scanner_worker_schema_default = {
|
|
|
178335
178448
|
};
|
|
178336
178449
|
|
|
178337
178450
|
// src/scan/code/worker/scanner-worker.ts
|
|
178451
|
+
var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
|
|
178452
|
+
var KILLED_WORKER_GRACE_MS = 500;
|
|
178338
178453
|
function createLazyWorkerResponseValidator(compile) {
|
|
178339
178454
|
let validator;
|
|
178340
178455
|
return () => {
|
|
@@ -178352,15 +178467,29 @@ function compileWorkerResponseValidator() {
|
|
|
178352
178467
|
$ref: "#/$defs/response"
|
|
178353
178468
|
});
|
|
178354
178469
|
}
|
|
178355
|
-
function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
|
|
178356
|
-
|
|
178470
|
+
function invokeScannerWorker(request, command, run = runScannerWorkerProcess, stripEnv = []) {
|
|
178471
|
+
return interpretWorkerProcess(request, command, run(processInput(request, command, stripEnv)));
|
|
178472
|
+
}
|
|
178473
|
+
function invokeScannerWorkerAsync(request, command, runAsync = runScannerWorkerProcessAsync, stripEnv = []) {
|
|
178474
|
+
const run = runAsync(processInput(request, command, stripEnv));
|
|
178475
|
+
return {
|
|
178476
|
+
promise: run.promise.then((processResult) => interpretWorkerProcess(request, command, processResult)),
|
|
178477
|
+
cancel: () => run.cancel()
|
|
178478
|
+
};
|
|
178479
|
+
}
|
|
178480
|
+
function processInput(request, command, stripEnv) {
|
|
178481
|
+
return {
|
|
178357
178482
|
command,
|
|
178358
|
-
stdin: JSON.stringify(request)
|
|
178359
|
-
|
|
178483
|
+
stdin: JSON.stringify(request),
|
|
178484
|
+
stripEnv,
|
|
178485
|
+
timeoutMs: workerTimeoutMs(request.files.length)
|
|
178486
|
+
};
|
|
178487
|
+
}
|
|
178488
|
+
function interpretWorkerProcess(request, command, processResult) {
|
|
178360
178489
|
if (!processResult.ok) {
|
|
178361
178490
|
return {
|
|
178362
178491
|
ok: false,
|
|
178363
|
-
diagnostic:
|
|
178492
|
+
diagnostic: processResult.kind === "execution" ? scannerFailedDiagnostic(request.language, reasonOf(processResult.error)) : scannerUnavailableDiagnostic(request.language, processResult.error, command[0]),
|
|
178364
178493
|
stderr: processResult.stderr
|
|
178365
178494
|
};
|
|
178366
178495
|
}
|
|
@@ -178401,43 +178530,203 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
|
|
|
178401
178530
|
}
|
|
178402
178531
|
function clangModuleCachePath() {
|
|
178403
178532
|
const owner = typeof process.getuid === "function" ? process.getuid() : "shared";
|
|
178404
|
-
return
|
|
178533
|
+
return join4(tmpdir(), `docbridge-clang-module-cache-${owner}`);
|
|
178534
|
+
}
|
|
178535
|
+
function workerProcessEnv(stripEnv = [], platform = process.platform) {
|
|
178536
|
+
const moduleCachePath = clangModuleCachePath();
|
|
178537
|
+
mkdirSync(moduleCachePath, { recursive: true });
|
|
178538
|
+
const fold = platform === "win32" ? (name) => name.toUpperCase() : (name) => name;
|
|
178539
|
+
const stripped = new Set(stripEnv.map(fold));
|
|
178540
|
+
const env = {};
|
|
178541
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
178542
|
+
if (value !== undefined && !stripped.has(fold(name))) {
|
|
178543
|
+
env[name] = value;
|
|
178544
|
+
}
|
|
178545
|
+
}
|
|
178546
|
+
env.CLANG_MODULE_CACHE_PATH = moduleCachePath;
|
|
178547
|
+
return env;
|
|
178548
|
+
}
|
|
178549
|
+
function workerTimeoutMs(fileCount) {
|
|
178550
|
+
return 30000 + 1000 * fileCount;
|
|
178405
178551
|
}
|
|
178406
178552
|
function runScannerWorkerProcess(input) {
|
|
178553
|
+
const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
|
|
178407
178554
|
try {
|
|
178408
|
-
const moduleCachePath = clangModuleCachePath();
|
|
178409
|
-
mkdirSync(moduleCachePath, { recursive: true });
|
|
178410
178555
|
const [executable = "", ...args] = input.command;
|
|
178411
178556
|
const result = spawnSync(executable, args, {
|
|
178412
|
-
env:
|
|
178413
|
-
...process.env,
|
|
178414
|
-
CLANG_MODULE_CACHE_PATH: moduleCachePath
|
|
178415
|
-
},
|
|
178557
|
+
env: workerProcessEnv(input.stripEnv),
|
|
178416
178558
|
input: input.stdin,
|
|
178417
178559
|
encoding: "utf8",
|
|
178418
|
-
maxBuffer:
|
|
178560
|
+
maxBuffer: maxOutputBytes,
|
|
178561
|
+
killSignal: "SIGKILL",
|
|
178562
|
+
...input.timeoutMs === undefined ? {} : { timeout: input.timeoutMs }
|
|
178419
178563
|
});
|
|
178420
|
-
|
|
178421
|
-
|
|
178422
|
-
|
|
178423
|
-
|
|
178424
|
-
|
|
178425
|
-
|
|
178426
|
-
|
|
178427
|
-
|
|
178428
|
-
|
|
178429
|
-
|
|
178564
|
+
return syncWorkerProcessResult(result, input.timeoutMs, maxOutputBytes);
|
|
178565
|
+
} catch (error) {
|
|
178566
|
+
return { ok: false, kind: "start", error, stderr: "" };
|
|
178567
|
+
}
|
|
178568
|
+
}
|
|
178569
|
+
function syncWorkerProcessResult(result, timeoutMs, maxOutputBytes) {
|
|
178570
|
+
const stderr = result.stderr ?? "";
|
|
178571
|
+
if (result.error !== undefined) {
|
|
178572
|
+
return syncSpawnFailure(result.error, workerStarted(result), timeoutMs, maxOutputBytes, stderr);
|
|
178573
|
+
}
|
|
178574
|
+
if (Buffer.byteLength(result.stdout ?? "") + Buffer.byteLength(stderr) > maxOutputBytes) {
|
|
178575
|
+
return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
|
|
178576
|
+
}
|
|
178577
|
+
if (typeof result.status !== "number") {
|
|
178578
|
+
return { ok: false, kind: "execution", error: signalError(result.signal), stderr };
|
|
178579
|
+
}
|
|
178580
|
+
return { ok: true, exitCode: result.status, stdout: result.stdout ?? "", stderr };
|
|
178581
|
+
}
|
|
178582
|
+
function runScannerWorkerProcessAsync(input) {
|
|
178583
|
+
const run = deferred();
|
|
178584
|
+
let settled = false;
|
|
178585
|
+
let timer;
|
|
178586
|
+
let grace;
|
|
178587
|
+
const settle = (complete) => {
|
|
178588
|
+
if (settled) {
|
|
178589
|
+
return;
|
|
178430
178590
|
}
|
|
178431
|
-
|
|
178432
|
-
|
|
178433
|
-
|
|
178434
|
-
|
|
178435
|
-
|
|
178436
|
-
|
|
178591
|
+
settled = true;
|
|
178592
|
+
clearTimeout(timer);
|
|
178593
|
+
clearTimeout(grace);
|
|
178594
|
+
complete();
|
|
178595
|
+
};
|
|
178596
|
+
const ownGroup = process.platform !== "win32";
|
|
178597
|
+
let child;
|
|
178598
|
+
try {
|
|
178599
|
+
const [executable = "", ...args] = input.command;
|
|
178600
|
+
child = spawn(executable, args, { env: workerProcessEnv(input.stripEnv), detached: ownGroup });
|
|
178437
178601
|
} catch (error) {
|
|
178438
|
-
|
|
178602
|
+
settle(() => run.resolve({ ok: false, kind: "start", error, stderr: "" }));
|
|
178603
|
+
return { promise: run.promise, cancel: () => {
|
|
178604
|
+
return;
|
|
178605
|
+
} };
|
|
178606
|
+
}
|
|
178607
|
+
const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
|
|
178608
|
+
const budget = { bytes: 0, limit: maxOutputBytes };
|
|
178609
|
+
const stdout = new OutputCollector(budget);
|
|
178610
|
+
const stderr = new OutputCollector(budget);
|
|
178611
|
+
let failure;
|
|
178612
|
+
const kill = (reason) => {
|
|
178613
|
+
failure ??= reason;
|
|
178614
|
+
killWorker(child, ownGroup);
|
|
178615
|
+
grace ??= setTimeout(() => {
|
|
178616
|
+
closeStreams(child);
|
|
178617
|
+
settle(() => run.resolve(closedProcessResult(null, null, failure, stdout, stderr)));
|
|
178618
|
+
}, KILLED_WORKER_GRACE_MS);
|
|
178619
|
+
};
|
|
178620
|
+
let spawned = false;
|
|
178621
|
+
child.on("spawn", () => {
|
|
178622
|
+
spawned = true;
|
|
178623
|
+
});
|
|
178624
|
+
child.on("error", (error) => {
|
|
178625
|
+
if (!spawned) {
|
|
178626
|
+
settle(() => run.resolve({ ok: false, kind: "start", error, stderr: stderr.text() }));
|
|
178627
|
+
return;
|
|
178628
|
+
}
|
|
178629
|
+
failure ??= error;
|
|
178630
|
+
});
|
|
178631
|
+
child.stdout.on("data", (chunk) => {
|
|
178632
|
+
if (!stdout.add(chunk)) {
|
|
178633
|
+
kill(outputLimitError(maxOutputBytes));
|
|
178634
|
+
}
|
|
178635
|
+
});
|
|
178636
|
+
child.stderr.on("data", (chunk) => {
|
|
178637
|
+
if (!stderr.add(chunk)) {
|
|
178638
|
+
kill(outputLimitError(maxOutputBytes));
|
|
178639
|
+
}
|
|
178640
|
+
});
|
|
178641
|
+
child.stdin.on("error", () => {
|
|
178642
|
+
return;
|
|
178643
|
+
});
|
|
178644
|
+
child.on("close", (code, signal) => {
|
|
178645
|
+
settle(() => run.resolve(closedProcessResult(code, signal, failure, stdout, stderr)));
|
|
178646
|
+
});
|
|
178647
|
+
child.stdin.end(input.stdin);
|
|
178648
|
+
const { timeoutMs } = input;
|
|
178649
|
+
if (timeoutMs !== undefined) {
|
|
178650
|
+
timer = setTimeout(() => kill(timeoutError(timeoutMs)), timeoutMs);
|
|
178651
|
+
}
|
|
178652
|
+
return {
|
|
178653
|
+
promise: run.promise,
|
|
178654
|
+
cancel() {
|
|
178655
|
+
settle(() => {
|
|
178656
|
+
killWorker(child, ownGroup);
|
|
178657
|
+
closeStreams(child);
|
|
178658
|
+
run.reject(abortError());
|
|
178659
|
+
});
|
|
178660
|
+
}
|
|
178661
|
+
};
|
|
178662
|
+
}
|
|
178663
|
+
function killWorker(child, ownGroup) {
|
|
178664
|
+
if (ownGroup && child.pid !== undefined) {
|
|
178665
|
+
try {
|
|
178666
|
+
process.kill(-child.pid, "SIGKILL");
|
|
178667
|
+
return;
|
|
178668
|
+
} catch {}
|
|
178669
|
+
}
|
|
178670
|
+
child.kill("SIGKILL");
|
|
178671
|
+
}
|
|
178672
|
+
function closeStreams(child) {
|
|
178673
|
+
child.stdin.destroy();
|
|
178674
|
+
child.stdout.destroy();
|
|
178675
|
+
child.stderr.destroy();
|
|
178676
|
+
}
|
|
178677
|
+
|
|
178678
|
+
class OutputCollector {
|
|
178679
|
+
budget;
|
|
178680
|
+
chunks = [];
|
|
178681
|
+
constructor(budget) {
|
|
178682
|
+
this.budget = budget;
|
|
178683
|
+
}
|
|
178684
|
+
add(chunk) {
|
|
178685
|
+
this.budget.bytes += chunk.byteLength;
|
|
178686
|
+
if (this.budget.bytes > this.budget.limit) {
|
|
178687
|
+
return false;
|
|
178688
|
+
}
|
|
178689
|
+
this.chunks.push(chunk);
|
|
178690
|
+
return true;
|
|
178691
|
+
}
|
|
178692
|
+
text() {
|
|
178693
|
+
return Buffer.concat(this.chunks).toString("utf8");
|
|
178439
178694
|
}
|
|
178440
178695
|
}
|
|
178696
|
+
function closedProcessResult(code, signal, failure, stdout, stderr) {
|
|
178697
|
+
if (failure !== undefined) {
|
|
178698
|
+
return { ok: false, kind: "execution", error: failure, stderr: stderr.text() };
|
|
178699
|
+
}
|
|
178700
|
+
if (code === null) {
|
|
178701
|
+
return { ok: false, kind: "execution", error: signalError(signal), stderr: stderr.text() };
|
|
178702
|
+
}
|
|
178703
|
+
return { ok: true, exitCode: code, stdout: stdout.text(), stderr: stderr.text() };
|
|
178704
|
+
}
|
|
178705
|
+
function workerStarted(result) {
|
|
178706
|
+
return typeof result.status === "number" || typeof result.signal === "string" || typeof result.pid === "number" && result.pid > 0;
|
|
178707
|
+
}
|
|
178708
|
+
function syncSpawnFailure(error, started, timeoutMs, maxOutputBytes, stderr) {
|
|
178709
|
+
const code = error.code;
|
|
178710
|
+
if (code === "ETIMEDOUT") {
|
|
178711
|
+
return { ok: false, kind: "execution", error: timeoutError(timeoutMs ?? 0), stderr };
|
|
178712
|
+
}
|
|
178713
|
+
if (code === "ENOBUFS") {
|
|
178714
|
+
return { ok: false, kind: "execution", error: outputLimitError(maxOutputBytes), stderr };
|
|
178715
|
+
}
|
|
178716
|
+
return { ok: false, kind: started ? "execution" : "start", error, stderr };
|
|
178717
|
+
}
|
|
178718
|
+
function signalError(signal) {
|
|
178719
|
+
const name = signal ?? "unknown";
|
|
178720
|
+
return Object.assign(new Error(`worker terminated by signal ${name}`), { signal: name });
|
|
178721
|
+
}
|
|
178722
|
+
function timeoutError(timeoutMs) {
|
|
178723
|
+
return Object.assign(new Error(`worker timed out after ${timeoutMs} ms`), {
|
|
178724
|
+
code: "ETIMEDOUT"
|
|
178725
|
+
});
|
|
178726
|
+
}
|
|
178727
|
+
function outputLimitError(maxOutputBytes) {
|
|
178728
|
+
return Object.assign(new Error(`worker wrote more than ${maxOutputBytes} bytes to stdout and stderr together`), { code: "ENOBUFS" });
|
|
178729
|
+
}
|
|
178441
178730
|
function validateWorkerResponse(value, request) {
|
|
178442
178731
|
const validator = responseValidator();
|
|
178443
178732
|
if (!validator(value)) {
|
|
@@ -178463,117 +178752,767 @@ function formatSchemaError(errors) {
|
|
|
178463
178752
|
const path = error.instancePath || "/";
|
|
178464
178753
|
return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
|
|
178465
178754
|
}
|
|
178466
|
-
function responseFilesMatchRequest(responseFiles, requestFiles) {
|
|
178467
|
-
if (responseFiles.length !== requestFiles.length) {
|
|
178468
|
-
return false;
|
|
178755
|
+
function responseFilesMatchRequest(responseFiles, requestFiles) {
|
|
178756
|
+
if (responseFiles.length !== requestFiles.length) {
|
|
178757
|
+
return false;
|
|
178758
|
+
}
|
|
178759
|
+
return responseFiles.every((file, index) => {
|
|
178760
|
+
if (!isRecord(file)) {
|
|
178761
|
+
return false;
|
|
178762
|
+
}
|
|
178763
|
+
return file.filePath === requestFiles[index]?.filePath;
|
|
178764
|
+
});
|
|
178765
|
+
}
|
|
178766
|
+
function isRecord(value) {
|
|
178767
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178768
|
+
}
|
|
178769
|
+
function scannerUnavailableDiagnostic(language, error, executable) {
|
|
178770
|
+
const label = languageLabel(language);
|
|
178771
|
+
return {
|
|
178772
|
+
severity: "error",
|
|
178773
|
+
code: "code_scanner_unavailable",
|
|
178774
|
+
language,
|
|
178775
|
+
target: language,
|
|
178776
|
+
message: `${label} scanner worker is unavailable: ${spawnFailureReason(error, executable)}`
|
|
178777
|
+
};
|
|
178778
|
+
}
|
|
178779
|
+
function spawnFailureReason(error, executable) {
|
|
178780
|
+
const reason = reasonOf(error);
|
|
178781
|
+
if (!isExecDenied(error) || executable === undefined) {
|
|
178782
|
+
return reason;
|
|
178783
|
+
}
|
|
178784
|
+
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\``;
|
|
178785
|
+
}
|
|
178786
|
+
function isExecDenied(error) {
|
|
178787
|
+
if (!(error instanceof Error)) {
|
|
178788
|
+
return false;
|
|
178789
|
+
}
|
|
178790
|
+
const code = error.code;
|
|
178791
|
+
return code === "EACCES" || code === "EPERM";
|
|
178792
|
+
}
|
|
178793
|
+
function scannerFailedDiagnostic(language, reason) {
|
|
178794
|
+
const label = languageLabel(language);
|
|
178795
|
+
return {
|
|
178796
|
+
severity: "error",
|
|
178797
|
+
code: "code_scanner_failed",
|
|
178798
|
+
language,
|
|
178799
|
+
target: language,
|
|
178800
|
+
message: `${label} scanner worker failed: ${reason}`
|
|
178801
|
+
};
|
|
178802
|
+
}
|
|
178803
|
+
function languageLabel(language) {
|
|
178804
|
+
return language.charAt(0).toUpperCase() + language.slice(1);
|
|
178805
|
+
}
|
|
178806
|
+
|
|
178807
|
+
// src/scan/code/worker/runtime-probe.ts
|
|
178808
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
178809
|
+
var PROBE_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
178810
|
+
function probeRuntime(command, stripEnv, spawn2 = spawnRuntimeProbe) {
|
|
178811
|
+
const [executable = "", ...args] = command;
|
|
178812
|
+
return classifyProbe(spawn2(executable, [...args, "--probe"], {
|
|
178813
|
+
env: workerProcessEnv(stripEnv),
|
|
178814
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
178815
|
+
maxBuffer: PROBE_MAX_OUTPUT_BYTES
|
|
178816
|
+
}));
|
|
178817
|
+
}
|
|
178818
|
+
function probeRuntimeAsync(command, stripEnv, runAsync = runScannerWorkerProcessAsync) {
|
|
178819
|
+
const run = runAsync({
|
|
178820
|
+
command: [...command, "--probe"],
|
|
178821
|
+
stdin: "",
|
|
178822
|
+
stripEnv,
|
|
178823
|
+
timeoutMs: PROBE_TIMEOUT_MS,
|
|
178824
|
+
maxOutputBytes: PROBE_MAX_OUTPUT_BYTES
|
|
178825
|
+
});
|
|
178826
|
+
return {
|
|
178827
|
+
promise: run.promise.then((result) => classifyProbe(spawnResultOf(result))),
|
|
178828
|
+
cancel: () => run.cancel()
|
|
178829
|
+
};
|
|
178830
|
+
}
|
|
178831
|
+
function spawnResultOf(result) {
|
|
178832
|
+
if (result.ok) {
|
|
178833
|
+
return { status: result.exitCode, signal: null, stdout: result.stdout, stderr: result.stderr };
|
|
178834
|
+
}
|
|
178835
|
+
const error = result.error instanceof Error ? result.error : new Error(reasonOf(result.error));
|
|
178836
|
+
const { signal } = error;
|
|
178837
|
+
if (typeof signal === "string") {
|
|
178838
|
+
return { status: null, signal, stdout: "", stderr: result.stderr };
|
|
178839
|
+
}
|
|
178840
|
+
return { status: null, signal: null, stdout: "", stderr: result.stderr, error };
|
|
178841
|
+
}
|
|
178842
|
+
function classifyProbe(result) {
|
|
178843
|
+
if (result.error !== undefined) {
|
|
178844
|
+
return classifySpawnError(result.error);
|
|
178845
|
+
}
|
|
178846
|
+
if (result.status === null) {
|
|
178847
|
+
return { kind: "failed", reason: `probe terminated by signal ${result.signal ?? "unknown"}` };
|
|
178848
|
+
}
|
|
178849
|
+
if (result.status !== 0) {
|
|
178850
|
+
const detail = firstLine(result.stderr);
|
|
178851
|
+
return {
|
|
178852
|
+
kind: "failed",
|
|
178853
|
+
reason: `probe exited with status ${result.status}${detail === "" ? "" : `: ${detail}`}`
|
|
178854
|
+
};
|
|
178855
|
+
}
|
|
178856
|
+
return parseProbeOutput(result.stdout);
|
|
178857
|
+
}
|
|
178858
|
+
function classifySpawnError(error) {
|
|
178859
|
+
const code = error.code;
|
|
178860
|
+
if (code === "ETIMEDOUT") {
|
|
178861
|
+
return { kind: "failed", reason: `probe did not finish within ${PROBE_TIMEOUT_MS / 1000} s` };
|
|
178862
|
+
}
|
|
178863
|
+
if (code === "ENOBUFS") {
|
|
178864
|
+
return {
|
|
178865
|
+
kind: "failed",
|
|
178866
|
+
reason: `probe printed more than ${PROBE_MAX_OUTPUT_BYTES / 1024} KiB`
|
|
178867
|
+
};
|
|
178868
|
+
}
|
|
178869
|
+
return { kind: "unstartable", reason: reasonOf(error) };
|
|
178870
|
+
}
|
|
178871
|
+
function parseProbeOutput(stdout) {
|
|
178872
|
+
const text = stdout.trim();
|
|
178873
|
+
let value;
|
|
178874
|
+
try {
|
|
178875
|
+
value = JSON.parse(text);
|
|
178876
|
+
} catch {
|
|
178877
|
+
return malformed(text);
|
|
178878
|
+
}
|
|
178879
|
+
if (!isRecord2(value)) {
|
|
178880
|
+
return malformed(text);
|
|
178881
|
+
}
|
|
178882
|
+
if (value.ok === true && typeof value.runtime === "string" && typeof value.version === "string") {
|
|
178883
|
+
return { kind: "ok", runtime: value.runtime, version: value.version };
|
|
178884
|
+
}
|
|
178885
|
+
if (value.ok === false && typeof value.reason === "string") {
|
|
178886
|
+
return { kind: "rejected", reason: value.reason };
|
|
178887
|
+
}
|
|
178888
|
+
return malformed(text);
|
|
178889
|
+
}
|
|
178890
|
+
function malformed(text) {
|
|
178891
|
+
const shown = text.length > 200 ? `${text.slice(0, 200)}...` : text;
|
|
178892
|
+
return { kind: "failed", reason: `malformed probe output: ${JSON.stringify(shown)}` };
|
|
178893
|
+
}
|
|
178894
|
+
function firstLine(text) {
|
|
178895
|
+
return text.trim().split(/\r?\n/u)[0] ?? "";
|
|
178896
|
+
}
|
|
178897
|
+
function isRecord2(value) {
|
|
178898
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178899
|
+
}
|
|
178900
|
+
function spawnRuntimeProbe(executable, args, options) {
|
|
178901
|
+
try {
|
|
178902
|
+
const result = spawnSync2(executable, args, {
|
|
178903
|
+
env: options.env,
|
|
178904
|
+
timeout: options.timeout,
|
|
178905
|
+
killSignal: "SIGKILL",
|
|
178906
|
+
maxBuffer: options.maxBuffer,
|
|
178907
|
+
input: "",
|
|
178908
|
+
encoding: "utf8",
|
|
178909
|
+
windowsHide: true
|
|
178910
|
+
});
|
|
178911
|
+
const stdout = result.stdout ?? "";
|
|
178912
|
+
const stderr = result.stderr ?? "";
|
|
178913
|
+
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;
|
|
178914
|
+
return {
|
|
178915
|
+
status: result.status,
|
|
178916
|
+
signal: result.signal,
|
|
178917
|
+
stdout,
|
|
178918
|
+
stderr,
|
|
178919
|
+
...error === undefined ? {} : { error }
|
|
178920
|
+
};
|
|
178921
|
+
} catch (error) {
|
|
178922
|
+
return {
|
|
178923
|
+
status: null,
|
|
178924
|
+
signal: null,
|
|
178925
|
+
stdout: "",
|
|
178926
|
+
stderr: "",
|
|
178927
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
178928
|
+
};
|
|
178929
|
+
}
|
|
178930
|
+
}
|
|
178931
|
+
|
|
178932
|
+
// src/scan/code/worker/scanner-executable.ts
|
|
178933
|
+
import { accessSync, chmodSync, constants, existsSync as existsSync2, statSync } from "node:fs";
|
|
178934
|
+
import { join as join6 } from "node:path";
|
|
178935
|
+
|
|
178936
|
+
// src/shared/package-root.ts
|
|
178937
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
178938
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
178939
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
178940
|
+
function resolvePackageRoot(moduleUrl = import.meta.url) {
|
|
178941
|
+
const modulePath = fileURLToPath2(moduleUrl);
|
|
178942
|
+
let resolved;
|
|
178943
|
+
try {
|
|
178944
|
+
resolved = realpathSync(modulePath);
|
|
178945
|
+
} catch {
|
|
178946
|
+
resolved = modulePath;
|
|
178947
|
+
}
|
|
178948
|
+
let dir = dirname2(resolved);
|
|
178949
|
+
for (;; ) {
|
|
178950
|
+
if (existsSync(join5(dir, "templates", "skills"))) {
|
|
178951
|
+
return dir;
|
|
178952
|
+
}
|
|
178953
|
+
const parent = dirname2(dir);
|
|
178954
|
+
if (parent === dir) {
|
|
178955
|
+
return dir;
|
|
178956
|
+
}
|
|
178957
|
+
dir = parent;
|
|
178958
|
+
}
|
|
178959
|
+
}
|
|
178960
|
+
|
|
178961
|
+
// src/scan/code/worker/scanner-executable.ts
|
|
178962
|
+
var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
|
|
178963
|
+
var SCANNER_EXECUTABLE_NAMES = {
|
|
178964
|
+
swift: "docbridge-swift-scanner",
|
|
178965
|
+
dart: "docbridge_dart_scanner",
|
|
178966
|
+
rust: "docbridge-rust-scanner",
|
|
178967
|
+
go: "docbridge-go-scanner"
|
|
178968
|
+
};
|
|
178969
|
+
function scannerPlatformKey() {
|
|
178970
|
+
return `${process.platform}-${process.arch}`;
|
|
178971
|
+
}
|
|
178972
|
+
function scannerExecutableName(language) {
|
|
178973
|
+
return SCANNER_EXECUTABLE_NAMES[language];
|
|
178974
|
+
}
|
|
178975
|
+
function resolveScannerWorkerCommand(language, options = {}) {
|
|
178976
|
+
const platformKey = options.platformKey ?? scannerPlatformKey();
|
|
178977
|
+
const platformSupported = isSupportedScannerPlatformKey(platformKey);
|
|
178978
|
+
const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
|
|
178979
|
+
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
178980
|
+
if (found !== undefined) {
|
|
178981
|
+
const repair = ensureExecutable(found, options.chmod ?? chmodSync);
|
|
178982
|
+
if (!repair.ok) {
|
|
178983
|
+
return { ok: false, diagnostic: scannerUnavailableDiagnostic2(language, repair.reason) };
|
|
178984
|
+
}
|
|
178985
|
+
return { ok: true, command: [found] };
|
|
178986
|
+
}
|
|
178987
|
+
if (!platformSupported) {
|
|
178988
|
+
return {
|
|
178989
|
+
ok: false,
|
|
178990
|
+
diagnostic: scannerUnavailableDiagnostic2(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
178991
|
+
};
|
|
178992
|
+
}
|
|
178993
|
+
return {
|
|
178994
|
+
ok: false,
|
|
178995
|
+
diagnostic: scannerUnavailableDiagnostic2(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
178996
|
+
};
|
|
178997
|
+
}
|
|
178998
|
+
function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
|
|
178999
|
+
const sourceRoot = options.sourceRoot ?? sourceRootPath();
|
|
179000
|
+
const distRoot = options.distRoot ?? distRootPath();
|
|
179001
|
+
const executable = scannerExecutableName(language);
|
|
179002
|
+
if (language === "swift") {
|
|
179003
|
+
return [
|
|
179004
|
+
join6(sourceRoot, "packages/swift-scanner/.build/release", executable),
|
|
179005
|
+
join6(sourceRoot, "packages/swift-scanner/.build/debug", executable),
|
|
179006
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179007
|
+
];
|
|
179008
|
+
}
|
|
179009
|
+
if (language === "rust") {
|
|
179010
|
+
return [
|
|
179011
|
+
join6(sourceRoot, "packages/rust-scanner/target/release", executable),
|
|
179012
|
+
join6(sourceRoot, "packages/rust-scanner/target/debug", executable),
|
|
179013
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179014
|
+
];
|
|
179015
|
+
}
|
|
179016
|
+
if (language === "go") {
|
|
179017
|
+
return [
|
|
179018
|
+
join6(sourceRoot, "packages/go-scanner/bin", executable),
|
|
179019
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179020
|
+
];
|
|
179021
|
+
}
|
|
179022
|
+
return [
|
|
179023
|
+
join6(sourceRoot, "packages/dart-scanner/bin", executable),
|
|
179024
|
+
...platformSupported ? [join6(distRoot, "bin", platformKey, executable)] : []
|
|
179025
|
+
];
|
|
179026
|
+
}
|
|
179027
|
+
function ensureExecutable(path, chmod) {
|
|
179028
|
+
let mode;
|
|
179029
|
+
try {
|
|
179030
|
+
mode = statSync(path).mode;
|
|
179031
|
+
} catch (error) {
|
|
179032
|
+
return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
|
|
179033
|
+
}
|
|
179034
|
+
if (isExecutableByThisProcess(path)) {
|
|
179035
|
+
return { ok: true };
|
|
179036
|
+
}
|
|
179037
|
+
try {
|
|
179038
|
+
chmod(path, mode | 73);
|
|
179039
|
+
} catch (error) {
|
|
179040
|
+
return {
|
|
179041
|
+
ok: false,
|
|
179042
|
+
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`
|
|
179043
|
+
};
|
|
179044
|
+
}
|
|
179045
|
+
return { ok: true };
|
|
179046
|
+
}
|
|
179047
|
+
function isExecutableByThisProcess(path) {
|
|
179048
|
+
try {
|
|
179049
|
+
accessSync(path, constants.X_OK);
|
|
179050
|
+
return true;
|
|
179051
|
+
} catch {
|
|
179052
|
+
return false;
|
|
179053
|
+
}
|
|
179054
|
+
}
|
|
179055
|
+
function formatMode(mode) {
|
|
179056
|
+
return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
|
|
179057
|
+
}
|
|
179058
|
+
function isSupportedScannerPlatformKey(platformKey) {
|
|
179059
|
+
return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
|
|
179060
|
+
}
|
|
179061
|
+
function scannerRootsFromModuleUrl(moduleUrl) {
|
|
179062
|
+
const packageRoot = resolvePackageRoot(moduleUrl);
|
|
179063
|
+
return { distRoot: join6(packageRoot, "dist"), sourceRoot: packageRoot };
|
|
179064
|
+
}
|
|
179065
|
+
function sourceRootPath() {
|
|
179066
|
+
return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
|
|
179067
|
+
}
|
|
179068
|
+
function distRootPath() {
|
|
179069
|
+
return scannerRootsFromModuleUrl(import.meta.url).distRoot;
|
|
179070
|
+
}
|
|
179071
|
+
function scannerUnavailableDiagnostic2(language, reason) {
|
|
179072
|
+
const label = language.charAt(0).toUpperCase() + language.slice(1);
|
|
179073
|
+
return {
|
|
179074
|
+
severity: "error",
|
|
179075
|
+
code: "code_scanner_unavailable",
|
|
179076
|
+
language,
|
|
179077
|
+
target: language,
|
|
179078
|
+
message: `${label} scanner worker is unavailable: ${reason}`
|
|
179079
|
+
};
|
|
179080
|
+
}
|
|
179081
|
+
|
|
179082
|
+
// src/scan/code/worker/runtime-worker.ts
|
|
179083
|
+
var RUNTIME_WORKERS = {
|
|
179084
|
+
python: {
|
|
179085
|
+
label: "Python",
|
|
179086
|
+
runtimeName: "CPython",
|
|
179087
|
+
runtimeId: "cpython",
|
|
179088
|
+
floor: [3, 10],
|
|
179089
|
+
floorText: "3.10",
|
|
179090
|
+
candidates: (platform) => platform === "win32" ? [["py", "-3"], ["python"]] : [["python3"], ["python"]],
|
|
179091
|
+
flags: ["-I", "-S"],
|
|
179092
|
+
stripEnv: ["PYTHONPATH", "PYTHONSTARTUP", "PYTHONHOME", "PYTHONSAFEPATH"],
|
|
179093
|
+
entrypoint: {
|
|
179094
|
+
source: "packages/python-scanner/docbridge_python_scanner.py",
|
|
179095
|
+
dist: "workers/python/docbridge_python_scanner.py"
|
|
179096
|
+
}
|
|
179097
|
+
},
|
|
179098
|
+
ruby: {
|
|
179099
|
+
label: "Ruby",
|
|
179100
|
+
runtimeName: "CRuby",
|
|
179101
|
+
runtimeId: "cruby",
|
|
179102
|
+
floor: [3, 3],
|
|
179103
|
+
floorText: "3.3",
|
|
179104
|
+
candidates: () => [["ruby"]],
|
|
179105
|
+
flags: ["--disable=gems,did_you_mean,error_highlight", "-W0"],
|
|
179106
|
+
stripEnv: ["RUBYOPT", "RUBYLIB", "PRISM_FFI_BACKEND"],
|
|
179107
|
+
entrypoint: {
|
|
179108
|
+
source: "packages/ruby-scanner/bin/docbridge-ruby-scanner",
|
|
179109
|
+
dist: "workers/ruby/bin/docbridge-ruby-scanner"
|
|
179110
|
+
}
|
|
179111
|
+
},
|
|
179112
|
+
java: {
|
|
179113
|
+
label: "Java",
|
|
179114
|
+
runtimeName: "JDK",
|
|
179115
|
+
runtimeId: "jdk",
|
|
179116
|
+
floor: [17, 0],
|
|
179117
|
+
floorText: "17",
|
|
179118
|
+
candidates: () => [["java"]],
|
|
179119
|
+
flags: ["-Xshare:auto", "-XX:TieredStopAtLevel=1", "-XX:+UseSerialGC", "-jar"],
|
|
179120
|
+
stripEnv: ["JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS"],
|
|
179121
|
+
entrypoint: {
|
|
179122
|
+
source: "packages/java-scanner/build/docbridge-java-scanner.jar",
|
|
179123
|
+
dist: "workers/java/docbridge-java-scanner.jar"
|
|
179124
|
+
},
|
|
179125
|
+
buildHint: "run `just build-java-scanner` in a source checkout"
|
|
179126
|
+
}
|
|
179127
|
+
};
|
|
179128
|
+
var probeCache = new Map;
|
|
179129
|
+
var probeCacheGeneration = 0;
|
|
179130
|
+
function clearRuntimeProbeCache() {
|
|
179131
|
+
probeCache.clear();
|
|
179132
|
+
probeCacheGeneration += 1;
|
|
179133
|
+
}
|
|
179134
|
+
function rememberProbe(key, generation, outcome) {
|
|
179135
|
+
if (generation === probeCacheGeneration) {
|
|
179136
|
+
probeCache.set(key, outcome);
|
|
179137
|
+
}
|
|
179138
|
+
}
|
|
179139
|
+
function resolveRuntimeWorkerCommand(language, options) {
|
|
179140
|
+
const env = options.env ?? process.env;
|
|
179141
|
+
const probe = options.probe ?? probeRuntime;
|
|
179142
|
+
const steps = resolutionSteps(language, options, env);
|
|
179143
|
+
let step = steps.next();
|
|
179144
|
+
while (step.done !== true) {
|
|
179145
|
+
step = steps.next(cachedProbe(step.value, env, probe));
|
|
179146
|
+
}
|
|
179147
|
+
return step.value;
|
|
179148
|
+
}
|
|
179149
|
+
function resolveRuntimeWorkerCommandAsync(language, options) {
|
|
179150
|
+
const env = options.env ?? process.env;
|
|
179151
|
+
const probeAsync = options.probeAsync ?? probeRuntimeAsync;
|
|
179152
|
+
return cancelableSequence(async (run) => {
|
|
179153
|
+
const steps = resolutionSteps(language, options, env);
|
|
179154
|
+
let step = steps.next();
|
|
179155
|
+
while (step.done !== true) {
|
|
179156
|
+
const request = step.value;
|
|
179157
|
+
step = steps.next(await run(() => cachedProbeAsync(request, env, probeAsync)));
|
|
179158
|
+
}
|
|
179159
|
+
return step.value;
|
|
179160
|
+
});
|
|
179161
|
+
}
|
|
179162
|
+
function* resolutionSteps(language, options, env) {
|
|
179163
|
+
const spec = RUNTIME_WORKERS[language];
|
|
179164
|
+
const entrypoint = findEntrypoint(spec, options);
|
|
179165
|
+
if (!entrypoint.ok) {
|
|
179166
|
+
return failure(spec, language, "code_scanner_unavailable", entrypoint.reason);
|
|
179167
|
+
}
|
|
179168
|
+
const platform = options.platform ?? process.platform;
|
|
179169
|
+
const commandFor = (runtime) => [...runtime, ...spec.flags, entrypoint.path];
|
|
179170
|
+
const usable = (command, runtime, verdict) => ({
|
|
179171
|
+
ok: true,
|
|
179172
|
+
command,
|
|
179173
|
+
stripEnv: spec.stripEnv,
|
|
179174
|
+
runtime: [verdict.runtime, verdict.version, locateExecutable(runtime[0] ?? "", env, platform)]
|
|
179175
|
+
});
|
|
179176
|
+
const override = explicitOverride(language, options, env);
|
|
179177
|
+
if (override !== undefined) {
|
|
179178
|
+
const runtime = override.argv.map((part, index) => index === 0 ? resolveExecutable(part, options.projectRoot) : part);
|
|
179179
|
+
const command = commandFor(runtime);
|
|
179180
|
+
const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
|
|
179181
|
+
if (verdict.ok) {
|
|
179182
|
+
return usable(command, runtime, verdict);
|
|
179183
|
+
}
|
|
179184
|
+
return failure(spec, language, verdict.code, `${override.source} (${runtime.join(" ")}) ${verdict.reason}; no other runtime is tried ` + `while ${override.source} is set`);
|
|
179185
|
+
}
|
|
179186
|
+
const rejections = [];
|
|
179187
|
+
for (const runtime of spec.candidates(platform)) {
|
|
179188
|
+
const command = commandFor(runtime);
|
|
179189
|
+
const verdict = judge(spec, yield { command, stripEnv: spec.stripEnv });
|
|
179190
|
+
if (verdict.ok) {
|
|
179191
|
+
return usable(command, runtime, verdict);
|
|
179192
|
+
}
|
|
179193
|
+
rejections.push({ runtime, verdict });
|
|
179194
|
+
}
|
|
179195
|
+
const outcomes = rejections.map(({ runtime, verdict }) => `${runtime.join(" ")} ${verdict.reason}`).join("; ");
|
|
179196
|
+
const code = rejections.find(({ verdict }) => verdict.startable)?.verdict.code ?? "code_scanner_unavailable";
|
|
179197
|
+
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)}`);
|
|
179198
|
+
}
|
|
179199
|
+
function findEntrypoint(spec, options) {
|
|
179200
|
+
const roots = scannerRootsFromModuleUrl(import.meta.url);
|
|
179201
|
+
const source = join7(options.sourceRoot ?? roots.sourceRoot, spec.entrypoint.source);
|
|
179202
|
+
const dist = join7(options.distRoot ?? roots.distRoot, spec.entrypoint.dist);
|
|
179203
|
+
const found = [source, dist].find((path) => existsSync3(path));
|
|
179204
|
+
if (found !== undefined) {
|
|
179205
|
+
return { ok: true, path: found };
|
|
179206
|
+
}
|
|
179207
|
+
const hint = spec.buildHint === undefined ? "" : `; ${spec.buildHint}`;
|
|
179208
|
+
return {
|
|
179209
|
+
ok: false,
|
|
179210
|
+
reason: `the bundled worker is missing; looked for ${source} and ${dist}${hint}`
|
|
179211
|
+
};
|
|
179212
|
+
}
|
|
179213
|
+
function explicitOverride(language, options, env) {
|
|
179214
|
+
if (options.command !== undefined && options.command.length > 0) {
|
|
179215
|
+
return { source: `scanners.${language}.command`, argv: options.command };
|
|
179216
|
+
}
|
|
179217
|
+
const variable = environmentVariable(language);
|
|
179218
|
+
const value = env[variable];
|
|
179219
|
+
if (value !== undefined && value !== "") {
|
|
179220
|
+
return { source: variable, argv: [value] };
|
|
179221
|
+
}
|
|
179222
|
+
return;
|
|
179223
|
+
}
|
|
179224
|
+
function environmentVariable(language) {
|
|
179225
|
+
return `DOCBRIDGE_${language.toUpperCase()}_RUNTIME`;
|
|
179226
|
+
}
|
|
179227
|
+
function resolveExecutable(executable, projectRoot) {
|
|
179228
|
+
if (isAbsolute2(executable) || !/[/\\]/u.test(executable)) {
|
|
179229
|
+
return executable;
|
|
179230
|
+
}
|
|
179231
|
+
return resolve2(projectRoot, executable);
|
|
179232
|
+
}
|
|
179233
|
+
function locateExecutable(executable, env, platform) {
|
|
179234
|
+
const candidates = /[/\\]/u.test(executable) ? [executable] : pathCandidates(executable, env, platform);
|
|
179235
|
+
const found = candidates.find((candidate) => isExecutableFile(candidate, platform));
|
|
179236
|
+
if (found === undefined) {
|
|
179237
|
+
return executable;
|
|
178469
179238
|
}
|
|
178470
|
-
|
|
178471
|
-
|
|
179239
|
+
try {
|
|
179240
|
+
return realpathSync2(found);
|
|
179241
|
+
} catch {
|
|
179242
|
+
return found;
|
|
179243
|
+
}
|
|
179244
|
+
}
|
|
179245
|
+
function pathCandidates(name, env, platform) {
|
|
179246
|
+
const windows = platform === "win32";
|
|
179247
|
+
const directories = (env.PATH ?? "").split(windows ? ";" : ":").filter((dir) => dir !== "");
|
|
179248
|
+
const extensions = windows && extname(name) === "" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
|
|
179249
|
+
return directories.flatMap((directory) => extensions.map((extension) => join7(directory, `${name}${extension}`)));
|
|
179250
|
+
}
|
|
179251
|
+
function isExecutableFile(path, platform) {
|
|
179252
|
+
try {
|
|
179253
|
+
if (!statSync2(path).isFile()) {
|
|
178472
179254
|
return false;
|
|
178473
179255
|
}
|
|
178474
|
-
|
|
178475
|
-
|
|
179256
|
+
if (platform !== "win32") {
|
|
179257
|
+
accessSync2(path, constants2.X_OK);
|
|
179258
|
+
}
|
|
179259
|
+
return true;
|
|
179260
|
+
} catch {
|
|
179261
|
+
return false;
|
|
179262
|
+
}
|
|
178476
179263
|
}
|
|
178477
|
-
function
|
|
178478
|
-
|
|
179264
|
+
function cachedProbe({ command, stripEnv }, env, probe) {
|
|
179265
|
+
const key = probeCacheKey(command, env);
|
|
179266
|
+
const cached = probeCache.get(key);
|
|
179267
|
+
if (cached !== undefined) {
|
|
179268
|
+
return cached;
|
|
179269
|
+
}
|
|
179270
|
+
const generation = probeCacheGeneration;
|
|
179271
|
+
const outcome = probe(command, stripEnv);
|
|
179272
|
+
rememberProbe(key, generation, outcome);
|
|
179273
|
+
return outcome;
|
|
178479
179274
|
}
|
|
178480
|
-
function
|
|
178481
|
-
const
|
|
178482
|
-
|
|
178483
|
-
|
|
178484
|
-
|
|
178485
|
-
|
|
178486
|
-
|
|
178487
|
-
|
|
178488
|
-
|
|
179275
|
+
function cachedProbeAsync({ command, stripEnv }, env, probeAsync) {
|
|
179276
|
+
const key = probeCacheKey(command, env);
|
|
179277
|
+
const cached = probeCache.get(key);
|
|
179278
|
+
if (cached !== undefined) {
|
|
179279
|
+
return settledCancelable(cached);
|
|
179280
|
+
}
|
|
179281
|
+
const generation = probeCacheGeneration;
|
|
179282
|
+
return mapCancelable(probeAsync(command, stripEnv), (outcome) => {
|
|
179283
|
+
rememberProbe(key, generation, outcome);
|
|
179284
|
+
return outcome;
|
|
179285
|
+
});
|
|
178489
179286
|
}
|
|
178490
|
-
function
|
|
178491
|
-
const
|
|
178492
|
-
|
|
178493
|
-
|
|
179287
|
+
function probeCacheKey(command, env) {
|
|
179288
|
+
const docbridgeVariables = Object.entries(env).filter(([name]) => name.startsWith("DOCBRIDGE_")).toSorted(([left], [right]) => left.localeCompare(right));
|
|
179289
|
+
return JSON.stringify([command, env.PATH ?? "", docbridgeVariables]);
|
|
179290
|
+
}
|
|
179291
|
+
function judge(spec, outcome) {
|
|
179292
|
+
switch (outcome.kind) {
|
|
179293
|
+
case "unstartable":
|
|
179294
|
+
return rejected("code_scanner_unavailable", false, `could not be started: ${outcome.reason}`);
|
|
179295
|
+
case "failed":
|
|
179296
|
+
return rejected("code_scanner_failed", true, `failed its probe: ${outcome.reason}`);
|
|
179297
|
+
case "rejected":
|
|
179298
|
+
return rejected("code_scanner_unavailable", true, `is not a usable ${spec.runtimeName} ${spec.floorText} or later: ${outcome.reason}`);
|
|
179299
|
+
case "ok":
|
|
179300
|
+
return judgeVersion(spec, outcome.runtime, outcome.version);
|
|
178494
179301
|
}
|
|
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
179302
|
}
|
|
178497
|
-
function
|
|
178498
|
-
if (
|
|
178499
|
-
return
|
|
179303
|
+
function judgeVersion(spec, runtime, version) {
|
|
179304
|
+
if (runtime !== spec.runtimeId) {
|
|
179305
|
+
return rejected("code_scanner_unavailable", true, `reports runtime ${runtime} ${version}, not ${spec.runtimeName}`);
|
|
178500
179306
|
}
|
|
178501
|
-
const
|
|
178502
|
-
|
|
179307
|
+
const match = /^(\d+)(?:\.(\d+))?/u.exec(version);
|
|
179308
|
+
if (match === null) {
|
|
179309
|
+
return rejected("code_scanner_failed", true, `reported an unreadable version: ${version}`);
|
|
179310
|
+
}
|
|
179311
|
+
const major = Number(match[1]);
|
|
179312
|
+
const minor = Number(match[2] ?? "0");
|
|
179313
|
+
const [floorMajor, floorMinor] = spec.floor;
|
|
179314
|
+
if (major < floorMajor || major === floorMajor && minor < floorMinor) {
|
|
179315
|
+
return rejected("code_scanner_unavailable", true, `is ${spec.runtimeName} ${version}, below the ${spec.floorText} floor`);
|
|
179316
|
+
}
|
|
179317
|
+
return { ok: true, runtime, version };
|
|
178503
179318
|
}
|
|
178504
|
-
function
|
|
178505
|
-
|
|
179319
|
+
function rejected(code, startable, reason) {
|
|
179320
|
+
return { ok: false, code, startable, reason };
|
|
179321
|
+
}
|
|
179322
|
+
function failure(spec, language, code, reason) {
|
|
179323
|
+
const state = code === "code_scanner_unavailable" ? "is unavailable" : "failed";
|
|
178506
179324
|
return {
|
|
178507
|
-
|
|
178508
|
-
|
|
178509
|
-
|
|
178510
|
-
|
|
178511
|
-
|
|
179325
|
+
ok: false,
|
|
179326
|
+
diagnostic: {
|
|
179327
|
+
severity: "error",
|
|
179328
|
+
code,
|
|
179329
|
+
...isCodeLanguage(language) ? { language } : {},
|
|
179330
|
+
target: language,
|
|
179331
|
+
message: `${spec.label} scanner worker ${state}: ${reason}`
|
|
179332
|
+
}
|
|
178512
179333
|
};
|
|
178513
179334
|
}
|
|
178514
|
-
function languageLabel(language) {
|
|
178515
|
-
return language.charAt(0).toUpperCase() + language.slice(1);
|
|
178516
|
-
}
|
|
178517
179335
|
|
|
178518
179336
|
// src/scan/code/dispatch.ts
|
|
178519
179337
|
function createScannerWorkerAdapter(language, command, adapterOptions = {}) {
|
|
179338
|
+
const { commandAsync } = adapterOptions;
|
|
179339
|
+
const bound = (context) => boundWorkerAdapter(language, workerCommand(command(context)), adapterOptions).adapter;
|
|
179340
|
+
const prepare = (context) => mapCancelable(commandAsync?.(context) ?? settledCancelable(command(context)), (value) => boundWorkerAdapter(language, workerCommand(value), adapterOptions));
|
|
179341
|
+
const scanBatchAsync = (files, options, context) => {
|
|
179342
|
+
if (files.length === 0) {
|
|
179343
|
+
return settledCancelable([]);
|
|
179344
|
+
}
|
|
179345
|
+
if (commandAsync === undefined) {
|
|
179346
|
+
return bound(context).scanFilesAsync(files, options, context);
|
|
179347
|
+
}
|
|
179348
|
+
return cancelableSequence(async (step) => {
|
|
179349
|
+
const { adapter } = await step(() => prepare(context));
|
|
179350
|
+
return step(() => adapter.scanFilesAsync(files, options, context));
|
|
179351
|
+
});
|
|
179352
|
+
};
|
|
178520
179353
|
return {
|
|
179354
|
+
language,
|
|
179355
|
+
scanFile: (filePath, content, options, context) => bound(context).scanFile(filePath, content, options, context),
|
|
179356
|
+
scanFiles: (files, options, context) => files.length === 0 ? [] : bound(context).scanFiles(files, options, context),
|
|
179357
|
+
scanFilesAsync: scanBatchAsync,
|
|
179358
|
+
prepare
|
|
179359
|
+
};
|
|
179360
|
+
}
|
|
179361
|
+
function boundWorkerAdapter(language, resolved, adapterOptions) {
|
|
179362
|
+
const argv = resolved.ok ? resolved.command : [];
|
|
179363
|
+
const runtime = resolved.ok ? resolved.runtime : [];
|
|
179364
|
+
const adapter = {
|
|
178521
179365
|
language,
|
|
178522
179366
|
scanFile(filePath, content, options, context) {
|
|
178523
|
-
const
|
|
178524
|
-
|
|
178525
|
-
|
|
178526
|
-
|
|
178527
|
-
|
|
178528
|
-
|
|
179367
|
+
const [scan] = adapter.scanFiles([{ filePath, content }], options, context);
|
|
179368
|
+
return scan ?? emptyScan(language, filePath);
|
|
179369
|
+
},
|
|
179370
|
+
scanFiles(files, options, context) {
|
|
179371
|
+
if (files.length === 0) {
|
|
179372
|
+
return [];
|
|
178529
179373
|
}
|
|
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);
|
|
179374
|
+
if (!resolved.ok) {
|
|
179375
|
+
return failedBatch(language, files, resolved.diagnostic);
|
|
178541
179376
|
}
|
|
178542
|
-
|
|
178543
|
-
|
|
178544
|
-
|
|
178545
|
-
|
|
178546
|
-
|
|
179377
|
+
const result = invokeScannerWorker(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.run, resolved.stripEnv);
|
|
179378
|
+
return result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic);
|
|
179379
|
+
},
|
|
179380
|
+
scanFilesAsync(files, options, context) {
|
|
179381
|
+
if (files.length === 0) {
|
|
179382
|
+
return settledCancelable([]);
|
|
179383
|
+
}
|
|
179384
|
+
if (!resolved.ok) {
|
|
179385
|
+
return settledCancelable(failedBatch(language, files, resolved.diagnostic));
|
|
179386
|
+
}
|
|
179387
|
+
const task = invokeScannerWorkerAsync(workerRequest(language, files, options, context, adapterOptions), resolved.command, adapterOptions.runAsync, resolved.stripEnv);
|
|
179388
|
+
return mapCancelable(task, (result) => result.ok ? result.codeFiles : failedBatch(language, files, result.diagnostic));
|
|
179389
|
+
},
|
|
179390
|
+
prepare: () => settledCancelable({ argv, runtime, adapter })
|
|
178547
179391
|
};
|
|
179392
|
+
return { argv, runtime, adapter };
|
|
178548
179393
|
}
|
|
178549
179394
|
function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
|
|
178550
|
-
const
|
|
178551
|
-
const
|
|
179395
|
+
const plan = planCodeScan(files, codeInclude, read, options.onContent);
|
|
179396
|
+
const context = scanContext(projectRoot, options);
|
|
179397
|
+
for (const batch of plan.batches) {
|
|
179398
|
+
const adapter = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
|
|
179399
|
+
fillBatch(plan, batch, adapter.scanFiles(batch.files, batch.options, context));
|
|
179400
|
+
}
|
|
179401
|
+
return assemblePlan(plan);
|
|
179402
|
+
}
|
|
179403
|
+
function scanContext(projectRoot, options) {
|
|
179404
|
+
return options.scanners === undefined ? { projectRoot } : { projectRoot, scanners: options.scanners };
|
|
179405
|
+
}
|
|
179406
|
+
function scanCodeFilesAsync(projectRoot, files, codeInclude, read, options = {}) {
|
|
179407
|
+
const plan = planCodeScan(files, codeInclude, read, options.onContent);
|
|
179408
|
+
const context = scanContext(projectRoot, options);
|
|
179409
|
+
return cancelableSequence(async (step) => {
|
|
179410
|
+
const cache = new Map;
|
|
179411
|
+
for (const batch of plan.batches) {
|
|
179412
|
+
const base = options.adapters?.[batch.language] ?? builtInAdapters[batch.language];
|
|
179413
|
+
const prepare = base.prepare?.bind(base);
|
|
179414
|
+
const { argv, runtime, adapter } = prepare === undefined ? { argv: [], adapter: base } : await step(() => prepare(context));
|
|
179415
|
+
const keys = batch.files.map((file) => codeScanCacheKey(batch.language, file.filePath, file.content, batch.options, argv, runtime));
|
|
179416
|
+
const cached = keys.map((key) => options.cache?.get(key));
|
|
179417
|
+
const missing = batch.files.filter((_, index) => cached[index] === undefined);
|
|
179418
|
+
const scanned = missing.length === 0 ? [] : await step(() => scanFilesAsync(adapter, missing, batch.options, context));
|
|
179419
|
+
let next = 0;
|
|
179420
|
+
const results = cached.map((hit) => hit ?? scanned[next++]);
|
|
179421
|
+
fillBatch(plan, batch, results);
|
|
179422
|
+
results.forEach((scan, index) => {
|
|
179423
|
+
const key = keys[index];
|
|
179424
|
+
if (scan !== undefined && key !== undefined && isReusableScan(scan)) {
|
|
179425
|
+
cache.set(key, scan);
|
|
179426
|
+
}
|
|
179427
|
+
});
|
|
179428
|
+
}
|
|
179429
|
+
return { ...assemblePlan(plan), cache };
|
|
179430
|
+
});
|
|
179431
|
+
}
|
|
179432
|
+
function scanFilesAsync(adapter, files, options, context) {
|
|
179433
|
+
return adapter.scanFilesAsync?.(files, options, context) ?? settledCancelable(adapter.scanFiles(files, options, context));
|
|
179434
|
+
}
|
|
179435
|
+
var builtInAdapters = {
|
|
179436
|
+
typescript: typeScriptAdapter,
|
|
179437
|
+
swift: createScannerWorkerAdapter("swift", () => resolveScannerWorkerCommand("swift")),
|
|
179438
|
+
dart: createScannerWorkerAdapter("dart", () => resolveScannerWorkerCommand("dart")),
|
|
179439
|
+
rust: createScannerWorkerAdapter("rust", () => resolveScannerWorkerCommand("rust")),
|
|
179440
|
+
go: createScannerWorkerAdapter("go", () => resolveScannerWorkerCommand("go")),
|
|
179441
|
+
javascript: javaScriptAdapter,
|
|
179442
|
+
python: runtimeWorkerAdapter("python"),
|
|
179443
|
+
ruby: runtimeWorkerAdapter("ruby")
|
|
179444
|
+
};
|
|
179445
|
+
function runtimeWorkerAdapter(language) {
|
|
179446
|
+
const resolution = ({ projectRoot, scanners }) => {
|
|
179447
|
+
const command = scanners?.[language]?.command;
|
|
179448
|
+
return command === undefined ? { projectRoot } : { projectRoot, command };
|
|
179449
|
+
};
|
|
179450
|
+
return createScannerWorkerAdapter(language, (context) => resolveRuntimeWorkerCommand(language, resolution(context)), { commandAsync: (context) => resolveRuntimeWorkerCommandAsync(language, resolution(context)) });
|
|
179451
|
+
}
|
|
179452
|
+
function planCodeScan(files, codeInclude, read, onContent) {
|
|
179453
|
+
const slots = [];
|
|
179454
|
+
const batches = new Map;
|
|
178552
179455
|
for (const { language, relPath } of files) {
|
|
178553
179456
|
const result = read(relPath);
|
|
178554
179457
|
if (!result.ok) {
|
|
178555
|
-
|
|
179458
|
+
slots.push({ read: false, diagnostic: result.diagnostic });
|
|
178556
179459
|
continue;
|
|
178557
179460
|
}
|
|
178558
|
-
|
|
178559
|
-
|
|
178560
|
-
|
|
178561
|
-
|
|
178562
|
-
|
|
178563
|
-
|
|
178564
|
-
});
|
|
179461
|
+
onContent?.(relPath, result.content);
|
|
179462
|
+
let batch = batches.get(language);
|
|
179463
|
+
if (batch === undefined) {
|
|
179464
|
+
batch = { language, options: scanOptionsFor(codeInclude, language), files: [], slots: [] };
|
|
179465
|
+
batches.set(language, batch);
|
|
179466
|
+
}
|
|
179467
|
+
batch.files.push({ filePath: relPath, content: result.content });
|
|
179468
|
+
batch.slots.push(slots.length);
|
|
179469
|
+
slots.push({ read: true, language, relPath });
|
|
179470
|
+
}
|
|
179471
|
+
return { slots, batches: [...batches.values()] };
|
|
179472
|
+
}
|
|
179473
|
+
function fillBatch(plan, batch, results) {
|
|
179474
|
+
batch.slots.forEach((slotIndex, resultIndex) => {
|
|
179475
|
+
const slot = plan.slots[slotIndex];
|
|
179476
|
+
const scan = results[resultIndex];
|
|
179477
|
+
if (slot?.read === true && scan !== undefined) {
|
|
179478
|
+
slot.scan = scan;
|
|
179479
|
+
}
|
|
179480
|
+
});
|
|
179481
|
+
}
|
|
179482
|
+
function assemblePlan(plan) {
|
|
179483
|
+
const codeFiles = [];
|
|
179484
|
+
const diagnostics = [];
|
|
179485
|
+
for (const slot of plan.slots) {
|
|
179486
|
+
if (!slot.read) {
|
|
179487
|
+
diagnostics.push(slot.diagnostic);
|
|
179488
|
+
continue;
|
|
179489
|
+
}
|
|
179490
|
+
const scan = slot.scan ?? emptyScan(slot.language, slot.relPath);
|
|
178565
179491
|
diagnostics.push(...scan.diagnostics);
|
|
178566
179492
|
codeFiles.push(scan);
|
|
178567
179493
|
}
|
|
178568
179494
|
return { codeFiles, diagnostics };
|
|
178569
179495
|
}
|
|
178570
|
-
|
|
178571
|
-
|
|
178572
|
-
|
|
178573
|
-
|
|
178574
|
-
|
|
178575
|
-
|
|
178576
|
-
|
|
179496
|
+
function scanOptionsFor(codeInclude, language) {
|
|
179497
|
+
const visibility = codeInclude[language]?.visibility;
|
|
179498
|
+
return visibility !== undefined ? { visibility } : {};
|
|
179499
|
+
}
|
|
179500
|
+
function workerRequest(language, files, options, context, adapterOptions) {
|
|
179501
|
+
return {
|
|
179502
|
+
schemaVersion: 1,
|
|
179503
|
+
requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
|
|
179504
|
+
language,
|
|
179505
|
+
projectRoot: context.projectRoot,
|
|
179506
|
+
files: files.map(({ filePath, content }) => ({ filePath, content })),
|
|
179507
|
+
options
|
|
179508
|
+
};
|
|
179509
|
+
}
|
|
179510
|
+
function failedBatch(language, files, diagnostic) {
|
|
179511
|
+
return files.map(({ filePath }) => ({
|
|
179512
|
+
...emptyScan(language, filePath),
|
|
179513
|
+
diagnostics: [{ ...diagnostic, target: filePath }]
|
|
179514
|
+
}));
|
|
179515
|
+
}
|
|
178577
179516
|
function emptyScan(language, filePath) {
|
|
178578
179517
|
return {
|
|
178579
179518
|
language,
|
|
@@ -178584,11 +179523,16 @@ function emptyScan(language, filePath) {
|
|
|
178584
179523
|
diagnostics: []
|
|
178585
179524
|
};
|
|
178586
179525
|
}
|
|
178587
|
-
function
|
|
178588
|
-
|
|
178589
|
-
}
|
|
178590
|
-
|
|
178591
|
-
return
|
|
179526
|
+
function workerCommand(value) {
|
|
179527
|
+
if (Array.isArray(value)) {
|
|
179528
|
+
return { ok: true, command: value, stripEnv: [], runtime: [] };
|
|
179529
|
+
}
|
|
179530
|
+
return value.ok ? {
|
|
179531
|
+
ok: true,
|
|
179532
|
+
command: value.command,
|
|
179533
|
+
stripEnv: value.stripEnv ?? [],
|
|
179534
|
+
runtime: value.runtime ?? []
|
|
179535
|
+
} : value;
|
|
178592
179536
|
}
|
|
178593
179537
|
|
|
178594
179538
|
// src/scan/markdown/markdown.ts
|
|
@@ -178823,6 +179767,42 @@ function flushDanglingWith(pending, diagnostics, message) {
|
|
|
178823
179767
|
|
|
178824
179768
|
// src/query/project-scan.ts
|
|
178825
179769
|
function scanProject(options) {
|
|
179770
|
+
const loaded = loadProjectScan(options, options.keepContent === true);
|
|
179771
|
+
if (!loaded.ok) {
|
|
179772
|
+
return loaded;
|
|
179773
|
+
}
|
|
179774
|
+
const { inputs } = loaded;
|
|
179775
|
+
const codeScan = scanCodeFiles(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, codeScanOptions(inputs, options));
|
|
179776
|
+
const docReads = readDocFiles(inputs, options);
|
|
179777
|
+
return { ok: true, scan: finishProjectScan(inputs, codeScan, docReads, options.buildGraph) };
|
|
179778
|
+
}
|
|
179779
|
+
function scanProjectAsync(options) {
|
|
179780
|
+
const loaded = loadProjectScan(options, true);
|
|
179781
|
+
if (!loaded.ok) {
|
|
179782
|
+
return cancelableSequence(async () => loaded);
|
|
179783
|
+
}
|
|
179784
|
+
const { inputs } = loaded;
|
|
179785
|
+
const fingerprint = JSON.stringify(inputs.config);
|
|
179786
|
+
const sameConfiguration = options.cache.fingerprint === fingerprint;
|
|
179787
|
+
if (!sameConfiguration && options.cache.fingerprint !== "") {
|
|
179788
|
+
options.onConfigurationChange?.();
|
|
179789
|
+
}
|
|
179790
|
+
const codeScan = scanCodeFilesAsync(options.projectRoot, inputs.codeFiles, inputs.config.include.code, inputs.readFile, {
|
|
179791
|
+
...codeScanOptions(inputs, options),
|
|
179792
|
+
...sameConfiguration ? { cache: options.cache.entries } : {}
|
|
179793
|
+
});
|
|
179794
|
+
const docReads = readDocFiles(inputs, options);
|
|
179795
|
+
return cancelableSequence(async (step) => {
|
|
179796
|
+
const { cache, ...code } = await step(codeScan);
|
|
179797
|
+
const scan = finishProjectScan(inputs, code, docReads, true);
|
|
179798
|
+
return {
|
|
179799
|
+
ok: true,
|
|
179800
|
+
scan,
|
|
179801
|
+
cache: { fingerprint, entries: cache }
|
|
179802
|
+
};
|
|
179803
|
+
});
|
|
179804
|
+
}
|
|
179805
|
+
function loadProjectScan(options, keepContent) {
|
|
178826
179806
|
const configResult = loadConfig(options.projectRoot);
|
|
178827
179807
|
if (!configResult.ok) {
|
|
178828
179808
|
return { ok: false, diagnostics: configResult.diagnostics };
|
|
@@ -178832,32 +179812,51 @@ function scanProject(options) {
|
|
|
178832
179812
|
return { ok: false, diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics] };
|
|
178833
179813
|
}
|
|
178834
179814
|
const collectCode = options.collectCode ?? collectCodeFiles;
|
|
178835
|
-
|
|
178836
|
-
|
|
178837
|
-
|
|
178838
|
-
|
|
178839
|
-
|
|
178840
|
-
|
|
178841
|
-
|
|
178842
|
-
|
|
179815
|
+
return {
|
|
179816
|
+
ok: true,
|
|
179817
|
+
inputs: {
|
|
179818
|
+
config: configResult.config,
|
|
179819
|
+
manifest: manifestResult.manifest,
|
|
179820
|
+
diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics],
|
|
179821
|
+
codeFiles: collectCode(options.projectRoot, configResult.config.include.code),
|
|
179822
|
+
readFile: options.readFile ?? ((relPath) => readManagedFile(options.projectRoot, relPath)),
|
|
179823
|
+
contentByFile: keepContent ? new Map : undefined
|
|
179824
|
+
}
|
|
179825
|
+
};
|
|
179826
|
+
}
|
|
179827
|
+
function codeScanOptions(inputs, options) {
|
|
179828
|
+
const { contentByFile } = inputs;
|
|
179829
|
+
const { scanners } = inputs.config;
|
|
179830
|
+
return {
|
|
178843
179831
|
...contentByFile === undefined ? {} : { onContent: (relPath, content) => contentByFile.set(relPath, content) },
|
|
178844
|
-
...options.adapters === undefined ? {} : { adapters: options.adapters }
|
|
179832
|
+
...options.adapters === undefined ? {} : { adapters: options.adapters },
|
|
179833
|
+
...scanners === undefined ? {} : { scanners }
|
|
179834
|
+
};
|
|
179835
|
+
}
|
|
179836
|
+
function readDocFiles(inputs, options) {
|
|
179837
|
+
const collectDocs = options.collectDocs ?? collectFiles;
|
|
179838
|
+
return collectDocs(options.projectRoot, inputs.config.include.docs).map((relPath) => {
|
|
179839
|
+
const read = inputs.readFile(relPath);
|
|
179840
|
+
if (read.ok) {
|
|
179841
|
+
inputs.contentByFile?.set(relPath, read.content);
|
|
179842
|
+
}
|
|
179843
|
+
return { relPath, read };
|
|
178845
179844
|
});
|
|
178846
|
-
|
|
179845
|
+
}
|
|
179846
|
+
function finishProjectScan(inputs, codeScan, docReads, buildGraph) {
|
|
179847
|
+
const diagnostics = [...inputs.diagnostics, ...codeScan.diagnostics];
|
|
178847
179848
|
const docFiles = [];
|
|
178848
|
-
for (const relPath of
|
|
178849
|
-
const read = readFile(relPath);
|
|
179849
|
+
for (const { relPath, read } of docReads) {
|
|
178850
179850
|
if (!read.ok) {
|
|
178851
179851
|
diagnostics.push(read.diagnostic);
|
|
178852
179852
|
continue;
|
|
178853
179853
|
}
|
|
178854
|
-
contentByFile?.set(relPath, read.content);
|
|
178855
179854
|
const scan2 = scanMarkdown(relPath, read.content);
|
|
178856
179855
|
diagnostics.push(...scan2.diagnostics);
|
|
178857
179856
|
docFiles.push(scan2);
|
|
178858
179857
|
}
|
|
178859
179858
|
const applied = applyLinkManifest({
|
|
178860
|
-
manifest:
|
|
179859
|
+
manifest: inputs.manifest,
|
|
178861
179860
|
codeFiles: codeScan.codeFiles,
|
|
178862
179861
|
docFiles,
|
|
178863
179862
|
scanDiagnostics: diagnostics
|
|
@@ -178869,25 +179868,26 @@ function scanProject(options) {
|
|
|
178869
179868
|
docFiles: applied.docFiles,
|
|
178870
179869
|
diagnostics
|
|
178871
179870
|
};
|
|
178872
|
-
if (contentByFile !== undefined) {
|
|
178873
|
-
scan.contentByFile = contentByFile;
|
|
179871
|
+
if (inputs.contentByFile !== undefined) {
|
|
179872
|
+
scan.contentByFile = inputs.contentByFile;
|
|
178874
179873
|
}
|
|
178875
|
-
if (
|
|
179874
|
+
if (buildGraph === true) {
|
|
178876
179875
|
scan.graph = buildLinkGraph(codeFiles, applied.docFiles);
|
|
178877
179876
|
}
|
|
178878
|
-
return
|
|
178879
|
-
ok: true,
|
|
178880
|
-
scan
|
|
178881
|
-
};
|
|
179877
|
+
return scan;
|
|
178882
179878
|
}
|
|
178883
179879
|
|
|
178884
179880
|
// src/lsp/project.ts
|
|
178885
179881
|
class Project {
|
|
178886
179882
|
projectRoot;
|
|
179883
|
+
options;
|
|
178887
179884
|
overlay = new Map;
|
|
179885
|
+
overlayRevision = 0;
|
|
178888
179886
|
current = emptyState();
|
|
178889
|
-
|
|
179887
|
+
cache = emptyCodeScanCache();
|
|
179888
|
+
constructor(projectRoot, options = {}) {
|
|
178890
179889
|
this.projectRoot = projectRoot;
|
|
179890
|
+
this.options = options;
|
|
178891
179891
|
}
|
|
178892
179892
|
get root() {
|
|
178893
179893
|
return this.projectRoot;
|
|
@@ -178897,48 +179897,57 @@ class Project {
|
|
|
178897
179897
|
}
|
|
178898
179898
|
setOverlay(relPath, content) {
|
|
178899
179899
|
this.overlay.set(relPath, content);
|
|
179900
|
+
this.overlayRevision += 1;
|
|
178900
179901
|
}
|
|
178901
179902
|
clearOverlay(relPath) {
|
|
178902
179903
|
this.overlay.delete(relPath);
|
|
179904
|
+
this.overlayRevision += 1;
|
|
178903
179905
|
}
|
|
178904
179906
|
resolve() {
|
|
178905
179907
|
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),
|
|
179908
|
+
...this.scanSources(this.overlay),
|
|
178910
179909
|
buildGraph: true,
|
|
178911
179910
|
keepContent: true
|
|
178912
179911
|
});
|
|
178913
|
-
|
|
178914
|
-
|
|
178915
|
-
|
|
178916
|
-
|
|
178917
|
-
|
|
179912
|
+
this.current = resolvedState(outcome);
|
|
179913
|
+
return this.current;
|
|
179914
|
+
}
|
|
179915
|
+
resolveAsync() {
|
|
179916
|
+
const revision = this.overlayRevision;
|
|
179917
|
+
const scan = scanProjectAsync({
|
|
179918
|
+
...this.scanSources(new Map(this.overlay)),
|
|
179919
|
+
cache: this.cache,
|
|
179920
|
+
onConfigurationChange: clearRuntimeProbeCache
|
|
179921
|
+
});
|
|
179922
|
+
let cancelled = false;
|
|
179923
|
+
const promise = scan.promise.then((outcome) => {
|
|
179924
|
+
if (cancelled || revision !== this.overlayRevision) {
|
|
179925
|
+
throw abortError();
|
|
179926
|
+
}
|
|
179927
|
+
if (outcome.ok) {
|
|
179928
|
+
this.cache = outcome.cache;
|
|
179929
|
+
}
|
|
179930
|
+
this.current = resolvedState(outcome);
|
|
178918
179931
|
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
179932
|
});
|
|
178933
|
-
|
|
178934
|
-
|
|
178935
|
-
|
|
178936
|
-
|
|
178937
|
-
|
|
179933
|
+
return {
|
|
179934
|
+
promise,
|
|
179935
|
+
cancel() {
|
|
179936
|
+
cancelled = true;
|
|
179937
|
+
scan.cancel();
|
|
179938
|
+
}
|
|
178938
179939
|
};
|
|
178939
|
-
return this.current;
|
|
178940
179940
|
}
|
|
178941
|
-
|
|
179941
|
+
scanSources(overlay) {
|
|
179942
|
+
return {
|
|
179943
|
+
projectRoot: this.projectRoot,
|
|
179944
|
+
collectCode: (_projectRoot, include) => this.collectCode(include, overlay),
|
|
179945
|
+
collectDocs: (_projectRoot, patterns) => this.collectDocs(patterns, overlay),
|
|
179946
|
+
readFile: (relPath) => this.readContent(relPath, overlay),
|
|
179947
|
+
...this.options.adapters === undefined ? {} : { adapters: this.options.adapters }
|
|
179948
|
+
};
|
|
179949
|
+
}
|
|
179950
|
+
collectCode(codeInclude, overlay) {
|
|
178942
179951
|
const onDisk = collectCodeFiles(this.projectRoot, codeInclude);
|
|
178943
179952
|
const seen = new Set(onDisk.map((file) => file.relPath));
|
|
178944
179953
|
const all = [...onDisk];
|
|
@@ -178947,11 +179956,11 @@ class Project {
|
|
|
178947
179956
|
if (entry === undefined) {
|
|
178948
179957
|
continue;
|
|
178949
179958
|
}
|
|
178950
|
-
for (const relPath of
|
|
179959
|
+
for (const relPath of overlay.keys()) {
|
|
178951
179960
|
if (seen.has(relPath)) {
|
|
178952
179961
|
continue;
|
|
178953
179962
|
}
|
|
178954
|
-
if (language
|
|
179963
|
+
if (hasExcludedSuffix(language, relPath)) {
|
|
178955
179964
|
continue;
|
|
178956
179965
|
}
|
|
178957
179966
|
if (entry.patterns.some((pattern) => matchGlob(pattern, relPath))) {
|
|
@@ -178962,22 +179971,19 @@ class Project {
|
|
|
178962
179971
|
}
|
|
178963
179972
|
return all.toSorted((left, right) => comparePaths(left.relPath, right.relPath));
|
|
178964
179973
|
}
|
|
178965
|
-
readContent(relPath) {
|
|
178966
|
-
const overlaid =
|
|
179974
|
+
readContent(relPath, overlay) {
|
|
179975
|
+
const overlaid = overlay.get(relPath);
|
|
178967
179976
|
if (overlaid !== undefined) {
|
|
178968
179977
|
return { ok: true, content: overlaid };
|
|
178969
179978
|
}
|
|
178970
179979
|
return readManagedFile(this.projectRoot, relPath);
|
|
178971
179980
|
}
|
|
178972
|
-
|
|
179981
|
+
collectDocs(patterns, overlay) {
|
|
178973
179982
|
const paths = new Set(collectFiles(this.projectRoot, patterns));
|
|
178974
|
-
for (const relPath of
|
|
179983
|
+
for (const relPath of overlay.keys()) {
|
|
178975
179984
|
if (paths.has(relPath)) {
|
|
178976
179985
|
continue;
|
|
178977
179986
|
}
|
|
178978
|
-
if (isCode && relPath.endsWith(".d.ts")) {
|
|
178979
|
-
continue;
|
|
178980
|
-
}
|
|
178981
179987
|
if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
|
|
178982
179988
|
paths.add(relPath);
|
|
178983
179989
|
}
|
|
@@ -178985,6 +179991,19 @@ class Project {
|
|
|
178985
179991
|
return [...paths].toSorted(comparePaths);
|
|
178986
179992
|
}
|
|
178987
179993
|
}
|
|
179994
|
+
function resolvedState(outcome) {
|
|
179995
|
+
if (!outcome.ok) {
|
|
179996
|
+
return { ...emptyState(), diagnostics: sortDiagnostics(outcome.diagnostics) };
|
|
179997
|
+
}
|
|
179998
|
+
const { codeFiles, docFiles, diagnostics: scanDiagnostics, contentByFile, graph } = outcome.scan;
|
|
179999
|
+
const relationship = resolveLinks({ codeFiles, docFiles, scanDiagnostics, audit: false });
|
|
180000
|
+
return {
|
|
180001
|
+
graph,
|
|
180002
|
+
index: buildPositionIndex(graph),
|
|
180003
|
+
diagnostics: sortDiagnostics([...scanDiagnostics, ...relationship]),
|
|
180004
|
+
contentByFile
|
|
180005
|
+
};
|
|
180006
|
+
}
|
|
178988
180007
|
function emptyState() {
|
|
178989
180008
|
const graph = buildLinkGraph([], []);
|
|
178990
180009
|
return {
|
|
@@ -178995,6 +180014,74 @@ function emptyState() {
|
|
|
178995
180014
|
};
|
|
178996
180015
|
}
|
|
178997
180016
|
|
|
180017
|
+
// src/lsp/scheduler.ts
|
|
180018
|
+
class RescanScheduler {
|
|
180019
|
+
options;
|
|
180020
|
+
generation = 0;
|
|
180021
|
+
dirty = false;
|
|
180022
|
+
debounce;
|
|
180023
|
+
active;
|
|
180024
|
+
stopped = false;
|
|
180025
|
+
constructor(options) {
|
|
180026
|
+
this.options = options;
|
|
180027
|
+
}
|
|
180028
|
+
request(when) {
|
|
180029
|
+
if (this.stopped) {
|
|
180030
|
+
return;
|
|
180031
|
+
}
|
|
180032
|
+
this.generation += 1;
|
|
180033
|
+
this.dirty = true;
|
|
180034
|
+
this.active?.task.cancel();
|
|
180035
|
+
this.clearDebounce();
|
|
180036
|
+
if (when === "debounced") {
|
|
180037
|
+
const handle = this.options.timers.setTimeout(() => {
|
|
180038
|
+
this.debounce = undefined;
|
|
180039
|
+
this.startIfReady();
|
|
180040
|
+
}, this.options.debounceMs);
|
|
180041
|
+
this.debounce = { handle };
|
|
180042
|
+
return;
|
|
180043
|
+
}
|
|
180044
|
+
this.startIfReady();
|
|
180045
|
+
}
|
|
180046
|
+
stop() {
|
|
180047
|
+
this.stopped = true;
|
|
180048
|
+
this.clearDebounce();
|
|
180049
|
+
this.active?.task.cancel();
|
|
180050
|
+
}
|
|
180051
|
+
startIfReady() {
|
|
180052
|
+
if (this.stopped || !this.dirty || this.active !== undefined || this.debounce !== undefined) {
|
|
180053
|
+
return;
|
|
180054
|
+
}
|
|
180055
|
+
this.dirty = false;
|
|
180056
|
+
const active = { generation: this.generation, task: this.options.scan() };
|
|
180057
|
+
this.active = active;
|
|
180058
|
+
active.task.promise.then((state) => this.settle(active, () => this.accept(active, state)), (error) => this.settle(active, () => this.fail(error)));
|
|
180059
|
+
}
|
|
180060
|
+
settle(active, report) {
|
|
180061
|
+
if (this.active === active) {
|
|
180062
|
+
this.active = undefined;
|
|
180063
|
+
}
|
|
180064
|
+
report();
|
|
180065
|
+
this.startIfReady();
|
|
180066
|
+
}
|
|
180067
|
+
accept(active, state) {
|
|
180068
|
+
if (!this.stopped && active.generation === this.generation) {
|
|
180069
|
+
this.options.onState(state);
|
|
180070
|
+
}
|
|
180071
|
+
}
|
|
180072
|
+
fail(error) {
|
|
180073
|
+
if (!isAbortError(error)) {
|
|
180074
|
+
this.options.onError(error);
|
|
180075
|
+
}
|
|
180076
|
+
}
|
|
180077
|
+
clearDebounce() {
|
|
180078
|
+
if (this.debounce !== undefined) {
|
|
180079
|
+
this.options.timers.clearTimeout(this.debounce.handle);
|
|
180080
|
+
this.debounce = undefined;
|
|
180081
|
+
}
|
|
180082
|
+
}
|
|
180083
|
+
}
|
|
180084
|
+
|
|
178998
180085
|
// src/lsp/transport.ts
|
|
178999
180086
|
var HEADER_SEPARATOR = `\r
|
|
179000
180087
|
\r
|
|
@@ -179062,6 +180149,10 @@ function parseContentLength(headerText) {
|
|
|
179062
180149
|
}
|
|
179063
180150
|
|
|
179064
180151
|
// src/lsp/server.ts
|
|
180152
|
+
var GLOBAL_TIMERS = {
|
|
180153
|
+
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
180154
|
+
clearTimeout: (handle) => clearTimeout(handle)
|
|
180155
|
+
};
|
|
179065
180156
|
var CAPABILITIES = {
|
|
179066
180157
|
textDocumentSync: 1,
|
|
179067
180158
|
hoverProvider: true,
|
|
@@ -179072,44 +180163,55 @@ var CAPABILITIES = {
|
|
|
179072
180163
|
class Server {
|
|
179073
180164
|
send;
|
|
179074
180165
|
project = null;
|
|
180166
|
+
scheduler = null;
|
|
179075
180167
|
openFiles = new Set;
|
|
179076
|
-
dirty = true;
|
|
179077
|
-
timer = null;
|
|
179078
180168
|
shuttingDown = false;
|
|
179079
180169
|
makeProject;
|
|
179080
180170
|
debounceMs;
|
|
179081
180171
|
onExit;
|
|
180172
|
+
timers;
|
|
180173
|
+
onScanError;
|
|
179082
180174
|
constructor(send, options = {}) {
|
|
179083
180175
|
this.send = send;
|
|
179084
180176
|
this.makeProject = options.makeProject ?? ((root) => new Project(root));
|
|
179085
180177
|
this.debounceMs = options.debounceMs ?? 50;
|
|
179086
180178
|
this.onExit = options.onExit ?? ((code) => process.exit(code));
|
|
180179
|
+
this.timers = options.timers ?? GLOBAL_TIMERS;
|
|
180180
|
+
this.onScanError = options.onScanError ?? ((error) => process.stderr.write(`docbridge lsp: scan failed: ${reasonOf(error)}
|
|
180181
|
+
`));
|
|
179087
180182
|
}
|
|
179088
180183
|
handle(message) {
|
|
179089
180184
|
switch (message.method) {
|
|
179090
180185
|
case "initialize":
|
|
179091
180186
|
this.respond(message.id, { capabilities: CAPABILITIES });
|
|
179092
|
-
this.
|
|
180187
|
+
this.start(this.makeProject(resolveRoot(message.params)));
|
|
179093
180188
|
break;
|
|
179094
180189
|
case "initialized":
|
|
179095
|
-
this.
|
|
180190
|
+
this.scheduler?.request("now");
|
|
179096
180191
|
break;
|
|
179097
180192
|
case "shutdown":
|
|
179098
180193
|
this.shuttingDown = true;
|
|
179099
|
-
this.
|
|
180194
|
+
this.scheduler?.stop();
|
|
179100
180195
|
this.respond(message.id, null);
|
|
179101
180196
|
break;
|
|
179102
180197
|
case "exit":
|
|
180198
|
+
this.scheduler?.stop();
|
|
179103
180199
|
this.onExit(this.shuttingDown ? 0 : 1);
|
|
179104
180200
|
break;
|
|
179105
180201
|
case "textDocument/didOpen":
|
|
179106
|
-
this.
|
|
180202
|
+
if (!this.shuttingDown) {
|
|
180203
|
+
this.onDidOpen(message.params);
|
|
180204
|
+
}
|
|
179107
180205
|
break;
|
|
179108
180206
|
case "textDocument/didChange":
|
|
179109
|
-
this.
|
|
180207
|
+
if (!this.shuttingDown) {
|
|
180208
|
+
this.onDidChange(message.params);
|
|
180209
|
+
}
|
|
179110
180210
|
break;
|
|
179111
180211
|
case "textDocument/didClose":
|
|
179112
|
-
this.
|
|
180212
|
+
if (!this.shuttingDown) {
|
|
180213
|
+
this.onDidClose(message.params);
|
|
180214
|
+
}
|
|
179113
180215
|
break;
|
|
179114
180216
|
case "textDocument/hover":
|
|
179115
180217
|
this.respond(message.id, this.onHover(message.params));
|
|
@@ -179126,6 +180228,16 @@ class Server {
|
|
|
179126
180228
|
}
|
|
179127
180229
|
}
|
|
179128
180230
|
}
|
|
180231
|
+
start(project) {
|
|
180232
|
+
this.project = project;
|
|
180233
|
+
this.scheduler = new RescanScheduler({
|
|
180234
|
+
scan: () => project.resolveAsync(),
|
|
180235
|
+
onState: (state) => this.publishOpenFiles(state),
|
|
180236
|
+
onError: this.onScanError,
|
|
180237
|
+
debounceMs: this.debounceMs,
|
|
180238
|
+
timers: this.timers
|
|
180239
|
+
});
|
|
180240
|
+
}
|
|
179129
180241
|
onDidOpen(params) {
|
|
179130
180242
|
const doc = textDocument(params);
|
|
179131
180243
|
const rel = this.relPath(doc?.uri);
|
|
@@ -179135,8 +180247,7 @@ class Server {
|
|
|
179135
180247
|
}
|
|
179136
180248
|
this.openFiles.add(rel);
|
|
179137
180249
|
this.project.setOverlay(rel, text);
|
|
179138
|
-
this.
|
|
179139
|
-
this.flush();
|
|
180250
|
+
this.scheduler?.request("now");
|
|
179140
180251
|
}
|
|
179141
180252
|
onDidChange(params) {
|
|
179142
180253
|
const record = params;
|
|
@@ -179147,7 +180258,7 @@ class Server {
|
|
|
179147
180258
|
return;
|
|
179148
180259
|
}
|
|
179149
180260
|
this.project.setOverlay(rel, text);
|
|
179150
|
-
this.
|
|
180261
|
+
this.scheduler?.request("debounced");
|
|
179151
180262
|
}
|
|
179152
180263
|
onDidClose(params) {
|
|
179153
180264
|
const doc = textDocument(params);
|
|
@@ -179157,9 +180268,8 @@ class Server {
|
|
|
179157
180268
|
}
|
|
179158
180269
|
this.openFiles.delete(rel);
|
|
179159
180270
|
this.project.clearOverlay(rel);
|
|
179160
|
-
this.dirty = true;
|
|
179161
180271
|
this.publish(rel, []);
|
|
179162
|
-
this.
|
|
180272
|
+
this.scheduler?.request("now");
|
|
179163
180273
|
}
|
|
179164
180274
|
onHover(params) {
|
|
179165
180275
|
const located = this.locate(params);
|
|
@@ -179198,7 +180308,6 @@ class Server {
|
|
|
179198
180308
|
if (rel === undefined || this.project === null || typeof position?.line !== "number" || typeof position?.character !== "number") {
|
|
179199
180309
|
return null;
|
|
179200
180310
|
}
|
|
179201
|
-
this.flush();
|
|
179202
180311
|
return {
|
|
179203
180312
|
state: this.project.state,
|
|
179204
180313
|
rel,
|
|
@@ -179206,28 +180315,14 @@ class Server {
|
|
|
179206
180315
|
position: fromLspPosition({ line: position.line, character: position.character })
|
|
179207
180316
|
};
|
|
179208
180317
|
}
|
|
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();
|
|
180318
|
+
publishOpenFiles(state) {
|
|
179224
180319
|
for (const rel of this.openFiles) {
|
|
179225
180320
|
const content = state.contentByFile.get(rel) ?? "";
|
|
179226
180321
|
this.publish(rel, diagnosticsForFile(state.diagnostics, rel, content));
|
|
179227
180322
|
}
|
|
179228
180323
|
}
|
|
179229
180324
|
publish(rel, diagnostics) {
|
|
179230
|
-
if (this.project === null) {
|
|
180325
|
+
if (this.project === null || this.shuttingDown) {
|
|
179231
180326
|
return;
|
|
179232
180327
|
}
|
|
179233
180328
|
this.send({
|
|
@@ -179248,12 +180343,6 @@ class Server {
|
|
|
179248
180343
|
}
|
|
179249
180344
|
this.send({ jsonrpc: "2.0", id, result });
|
|
179250
180345
|
}
|
|
179251
|
-
clearTimer() {
|
|
179252
|
-
if (this.timer !== null) {
|
|
179253
|
-
clearTimeout(this.timer);
|
|
179254
|
-
this.timer = null;
|
|
179255
|
-
}
|
|
179256
|
-
}
|
|
179257
180346
|
}
|
|
179258
180347
|
function resolveRoot(params) {
|
|
179259
180348
|
const record = params;
|
|
@@ -179347,7 +180436,7 @@ function check(options) {
|
|
|
179347
180436
|
}
|
|
179348
180437
|
|
|
179349
180438
|
// src/query/related.ts
|
|
179350
|
-
import { isAbsolute as
|
|
180439
|
+
import { isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
|
|
179351
180440
|
function normalizeChangedPaths(projectRoot, paths) {
|
|
179352
180441
|
const normalized = new Set;
|
|
179353
180442
|
for (const raw of paths) {
|
|
@@ -179355,7 +180444,7 @@ function normalizeChangedPaths(projectRoot, paths) {
|
|
|
179355
180444
|
if (trimmed === "") {
|
|
179356
180445
|
continue;
|
|
179357
180446
|
}
|
|
179358
|
-
const relativized =
|
|
180447
|
+
const relativized = isAbsolute3(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
|
|
179359
180448
|
normalized.add(relativized.replace(/^(\.\/)+/, ""));
|
|
179360
180449
|
}
|
|
179361
180450
|
return [...normalized];
|
|
@@ -179777,7 +180866,7 @@ function comparePairs(left, right) {
|
|
|
179777
180866
|
// src/setup/update-cache.ts
|
|
179778
180867
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
179779
180868
|
import { homedir } from "node:os";
|
|
179780
|
-
import { dirname as dirname3, join as
|
|
180869
|
+
import { dirname as dirname3, join as join8 } from "node:path";
|
|
179781
180870
|
var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
179782
180871
|
var UPDATE_CACHE_SCHEMA_VERSION = 1;
|
|
179783
180872
|
function resolveUpdateCachePath(env = process.env, home = homedir()) {
|
|
@@ -179785,8 +180874,8 @@ function resolveUpdateCachePath(env = process.env, home = homedir()) {
|
|
|
179785
180874
|
if (explicit !== undefined && explicit.length > 0) {
|
|
179786
180875
|
return explicit;
|
|
179787
180876
|
}
|
|
179788
|
-
const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME :
|
|
179789
|
-
return
|
|
180877
|
+
const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join8(home, ".cache");
|
|
180878
|
+
return join8(cacheRoot, "docbridge", "update-check.json");
|
|
179790
180879
|
}
|
|
179791
180880
|
function readUpdateCache(path) {
|
|
179792
180881
|
let rawText;
|
|
@@ -180030,7 +181119,7 @@ function isEnabledFlag(value) {
|
|
|
180030
181119
|
|
|
180031
181120
|
// src/cli/docs.ts
|
|
180032
181121
|
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
180033
|
-
import { join as
|
|
181122
|
+
import { join as join9 } from "node:path";
|
|
180034
181123
|
|
|
180035
181124
|
// src/cli/errors.ts
|
|
180036
181125
|
class CliError extends Error {
|
|
@@ -180201,7 +181290,7 @@ function parseDocument(name, source) {
|
|
|
180201
181290
|
};
|
|
180202
181291
|
}
|
|
180203
181292
|
function createFileDocumentationReader(packageRoot) {
|
|
180204
|
-
const docsRoot =
|
|
181293
|
+
const docsRoot = join9(packageRoot, "docs", "user");
|
|
180205
181294
|
function documents() {
|
|
180206
181295
|
let fileNames;
|
|
180207
181296
|
try {
|
|
@@ -180214,7 +181303,7 @@ function createFileDocumentationReader(packageRoot) {
|
|
|
180214
181303
|
}
|
|
180215
181304
|
return fileNames.map((fileName) => {
|
|
180216
181305
|
const name = fileName.slice(0, -".md".length);
|
|
180217
|
-
return parseDocument(name, readFileSync5(
|
|
181306
|
+
return parseDocument(name, readFileSync5(join9(docsRoot, fileName), "utf8"));
|
|
180218
181307
|
});
|
|
180219
181308
|
}
|
|
180220
181309
|
return {
|
|
@@ -180611,12 +181700,12 @@ function hasHelpFlag(args) {
|
|
|
180611
181700
|
}
|
|
180612
181701
|
|
|
180613
181702
|
// src/cli/init.ts
|
|
180614
|
-
import { readFileSync as readFileSync8, statSync as
|
|
180615
|
-
import { join as
|
|
181703
|
+
import { readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
181704
|
+
import { join as join13, resolve as resolve3 } from "node:path";
|
|
180616
181705
|
|
|
180617
181706
|
// src/setup/init-discovery.ts
|
|
180618
|
-
import { existsSync as
|
|
180619
|
-
import { basename, dirname as dirname4, join as
|
|
181707
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
181708
|
+
import { basename, dirname as dirname4, join as join10 } from "node:path";
|
|
180620
181709
|
var HIGH_CONFIDENCE_DIR_NAMES = new Set([
|
|
180621
181710
|
"docs/specs",
|
|
180622
181711
|
"specs",
|
|
@@ -180646,22 +181735,39 @@ var EXCLUDED_DIR_SEGMENTS = new Set([
|
|
|
180646
181735
|
"contributing"
|
|
180647
181736
|
]);
|
|
180648
181737
|
var IGNORED_WALK_SEGMENTS = new Set(["node_modules", ".git", "dist", "build"]);
|
|
180649
|
-
var
|
|
180650
|
-
|
|
180651
|
-
|
|
180652
|
-
"packages/*/src/**/*.ts",
|
|
180653
|
-
"apps/*/src/**/*.ts"
|
|
180654
|
-
];
|
|
181738
|
+
var SCRIPT_SOURCE_ROOTS = ["src", "lib", "packages/*/src", "apps/*/src"];
|
|
181739
|
+
var TYPESCRIPT_PATTERNS = scriptPatterns("typescript");
|
|
181740
|
+
var JAVASCRIPT_PATTERNS = scriptPatterns("javascript");
|
|
180655
181741
|
var SWIFT_PATTERNS = ["Sources/**/*.swift", "*/Sources/**/*.swift"];
|
|
180656
181742
|
var DART_PATTERNS = ["lib/**/*.dart"];
|
|
180657
181743
|
var RUST_PATTERNS = ["src/**/*.rs", "*/src/**/*.rs"];
|
|
180658
181744
|
var GO_PATTERNS = ["*.go", "cmd/**/*.go", "internal/**/*.go", "pkg/**/*.go"];
|
|
181745
|
+
var RUBY_PATTERNS = ["lib/**/*.rb", "app/**/*.rb"];
|
|
180659
181746
|
var LANGUAGE_PATTERNS = {
|
|
180660
181747
|
typescript: TYPESCRIPT_PATTERNS,
|
|
180661
181748
|
swift: SWIFT_PATTERNS,
|
|
180662
181749
|
dart: DART_PATTERNS,
|
|
180663
181750
|
rust: RUST_PATTERNS,
|
|
180664
|
-
go: GO_PATTERNS
|
|
181751
|
+
go: GO_PATTERNS,
|
|
181752
|
+
javascript: JAVASCRIPT_PATTERNS,
|
|
181753
|
+
ruby: RUBY_PATTERNS
|
|
181754
|
+
};
|
|
181755
|
+
var EXCLUDED_CODE_SEGMENTS = {
|
|
181756
|
+
typescript: ["__tests__", "tests", "test"],
|
|
181757
|
+
swift: ["Tests", "tests"],
|
|
181758
|
+
dart: ["test"],
|
|
181759
|
+
rust: ["target", "tests", "benches", "examples"],
|
|
181760
|
+
go: ["vendor", "testdata"],
|
|
181761
|
+
javascript: ["__tests__", "tests", "test"],
|
|
181762
|
+
python: ["tests", "venv", "dist", "site-packages"],
|
|
181763
|
+
ruby: ["spec", "test", "vendor"]
|
|
181764
|
+
};
|
|
181765
|
+
var TEST_FILE_PATTERNS = {
|
|
181766
|
+
typescript: /\.(?:test|spec)\.[cm]?tsx?$/u,
|
|
181767
|
+
swift: /tests\.swift$/u,
|
|
181768
|
+
dart: /_test\.dart$/u,
|
|
181769
|
+
go: /_test\.go$/u,
|
|
181770
|
+
javascript: /\.(?:test|spec)\.[cm]?jsx?$/u
|
|
180665
181771
|
};
|
|
180666
181772
|
function discoverRepository(projectRoot) {
|
|
180667
181773
|
return {
|
|
@@ -180745,8 +181851,8 @@ function discoverCodeScope(projectRoot) {
|
|
|
180745
181851
|
};
|
|
180746
181852
|
}
|
|
180747
181853
|
function discoverAgentTarget(projectRoot) {
|
|
180748
|
-
const hasAgentsDir =
|
|
180749
|
-
const hasClaudeDir =
|
|
181854
|
+
const hasAgentsDir = existsSync4(join10(projectRoot, ".agents"));
|
|
181855
|
+
const hasClaudeDir = existsSync4(join10(projectRoot, ".claude"));
|
|
180750
181856
|
let defaultTarget = "none";
|
|
180751
181857
|
let recommendedTarget = "none";
|
|
180752
181858
|
let message;
|
|
@@ -180773,7 +181879,7 @@ function discoverAgentTarget(projectRoot) {
|
|
|
180773
181879
|
};
|
|
180774
181880
|
}
|
|
180775
181881
|
function collectMarkdownFiles(projectRoot, currentDir = ".") {
|
|
180776
|
-
const absoluteDir =
|
|
181882
|
+
const absoluteDir = join10(projectRoot, currentDir);
|
|
180777
181883
|
let entries;
|
|
180778
181884
|
try {
|
|
180779
181885
|
entries = readdirSync3(absoluteDir);
|
|
@@ -180787,10 +181893,10 @@ function collectMarkdownFiles(projectRoot, currentDir = ".") {
|
|
|
180787
181893
|
if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
|
|
180788
181894
|
continue;
|
|
180789
181895
|
}
|
|
180790
|
-
const absolutePath =
|
|
181896
|
+
const absolutePath = join10(projectRoot, relPath);
|
|
180791
181897
|
let stats;
|
|
180792
181898
|
try {
|
|
180793
|
-
stats =
|
|
181899
|
+
stats = statSync3(absolutePath);
|
|
180794
181900
|
} catch {
|
|
180795
181901
|
continue;
|
|
180796
181902
|
}
|
|
@@ -180828,8 +181934,22 @@ function scoreDocsDirectory(directory) {
|
|
|
180828
181934
|
}
|
|
180829
181935
|
return score;
|
|
180830
181936
|
}
|
|
181937
|
+
function scriptPatterns(language) {
|
|
181938
|
+
return SCRIPT_SOURCE_ROOTS.flatMap((root) => LANGUAGE_SUFFIXES[language].map((suffix) => `${root}/**/*${suffix}`));
|
|
181939
|
+
}
|
|
180831
181940
|
function activeCodePatterns(projectRoot, language) {
|
|
180832
|
-
|
|
181941
|
+
const candidates = language === "python" ? pythonPatterns(projectRoot) : LANGUAGE_PATTERNS[language];
|
|
181942
|
+
return candidates.filter((pattern) => countCodeFiles(projectRoot, [pattern], language) > 0);
|
|
181943
|
+
}
|
|
181944
|
+
function pythonPatterns(projectRoot) {
|
|
181945
|
+
let entries;
|
|
181946
|
+
try {
|
|
181947
|
+
entries = readdirSync3(projectRoot, { withFileTypes: true });
|
|
181948
|
+
} catch {
|
|
181949
|
+
return [];
|
|
181950
|
+
}
|
|
181951
|
+
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();
|
|
181952
|
+
return ["src/**/*.py", ...packages.map((name) => `${name}/**/*.py`)];
|
|
180833
181953
|
}
|
|
180834
181954
|
function countCodeFiles(projectRoot, patterns, language) {
|
|
180835
181955
|
const seen = new Set;
|
|
@@ -180845,33 +181965,8 @@ function countCodeFiles(projectRoot, patterns, language) {
|
|
|
180845
181965
|
function isExcludedCodeFile(filePath, language) {
|
|
180846
181966
|
const lower = filePath.toLowerCase();
|
|
180847
181967
|
const segments = filePath.split("/");
|
|
180848
|
-
if (language ===
|
|
180849
|
-
|
|
180850
|
-
return true;
|
|
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
|
-
}
|
|
181968
|
+
if (hasExcludedSuffix(language, lower) || TEST_FILE_PATTERNS[language]?.test(lower) === true || EXCLUDED_CODE_SEGMENTS[language].some((segment) => segments.includes(segment))) {
|
|
181969
|
+
return true;
|
|
180875
181970
|
}
|
|
180876
181971
|
if (segments.some((segment) => ["generated", "gen", ".dart_tool", "build"].includes(segment.toLowerCase()))) {
|
|
180877
181972
|
return true;
|
|
@@ -180880,17 +181975,17 @@ function isExcludedCodeFile(filePath, language) {
|
|
|
180880
181975
|
}
|
|
180881
181976
|
|
|
180882
181977
|
// src/setup/init-plan.ts
|
|
180883
|
-
import { existsSync as
|
|
180884
|
-
import { join as
|
|
181978
|
+
import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
181979
|
+
import { join as join12, relative as relative3 } from "node:path";
|
|
180885
181980
|
|
|
180886
181981
|
// src/setup/skill-assets.ts
|
|
180887
181982
|
import { cpSync, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
180888
|
-
import { join as
|
|
181983
|
+
import { join as join11 } from "node:path";
|
|
180889
181984
|
function classifyManagedPath(projectRoot, relativePath) {
|
|
180890
181985
|
const segments = relativePath.split(/[/\\]/).filter((segment) => segment.length > 0);
|
|
180891
181986
|
let current = projectRoot;
|
|
180892
181987
|
for (const [index, segment] of segments.entries()) {
|
|
180893
|
-
current =
|
|
181988
|
+
current = join11(current, segment);
|
|
180894
181989
|
const isLast = index === segments.length - 1;
|
|
180895
181990
|
let stats;
|
|
180896
181991
|
try {
|
|
@@ -180937,7 +182032,7 @@ function compareSkillTree(installedDir, templateDir) {
|
|
|
180937
182032
|
continue;
|
|
180938
182033
|
}
|
|
180939
182034
|
installedFiles.delete(file);
|
|
180940
|
-
if (readFileOrUndefined(
|
|
182035
|
+
if (readFileOrUndefined(join11(installedDir, file)) !== readFileOrUndefined(join11(templateDir, file))) {
|
|
180941
182036
|
modified.push(file);
|
|
180942
182037
|
}
|
|
180943
182038
|
}
|
|
@@ -180948,7 +182043,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180948
182043
|
return;
|
|
180949
182044
|
}
|
|
180950
182045
|
const kind = classifyManagedPath(projectRoot, operation.path);
|
|
180951
|
-
const destinationDir =
|
|
182046
|
+
const destinationDir = join11(projectRoot, operation.path);
|
|
180952
182047
|
if (operation.action === "remove") {
|
|
180953
182048
|
if (kind === "directory") {
|
|
180954
182049
|
rmSync(destinationDir, { recursive: true, force: true });
|
|
@@ -180966,7 +182061,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180966
182061
|
rmSync(destinationDir, { recursive: true, force: true });
|
|
180967
182062
|
}
|
|
180968
182063
|
mkdirSync3(destinationDir, { recursive: true });
|
|
180969
|
-
cpSync(
|
|
182064
|
+
cpSync(join11(packageRoot, "templates", "skills", skillName), destinationDir, {
|
|
180970
182065
|
recursive: true,
|
|
180971
182066
|
force: true
|
|
180972
182067
|
});
|
|
@@ -180974,7 +182069,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180974
182069
|
function collectSkillFiles(root, prefix, files) {
|
|
180975
182070
|
let entries;
|
|
180976
182071
|
try {
|
|
180977
|
-
entries = readdirSync4(
|
|
182072
|
+
entries = readdirSync4(join11(root, prefix), { withFileTypes: true });
|
|
180978
182073
|
} catch {
|
|
180979
182074
|
return;
|
|
180980
182075
|
}
|
|
@@ -181085,14 +182180,14 @@ function buildConfigFromScope(scope) {
|
|
|
181085
182180
|
};
|
|
181086
182181
|
}
|
|
181087
182182
|
function listDistributableSkills(packageRoot) {
|
|
181088
|
-
const skillsRoot =
|
|
182183
|
+
const skillsRoot = join12(packageRoot, "templates", "skills");
|
|
181089
182184
|
let entries;
|
|
181090
182185
|
try {
|
|
181091
182186
|
entries = readdirSync5(skillsRoot);
|
|
181092
182187
|
} catch {
|
|
181093
182188
|
return [];
|
|
181094
182189
|
}
|
|
181095
|
-
return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) =>
|
|
182190
|
+
return entries.filter((entry) => entry.startsWith("docbridge")).filter((entry) => existsSync5(join12(skillsRoot, entry, "SKILL.md"))).toSorted();
|
|
181096
182191
|
}
|
|
181097
182192
|
function formatInitPlan(plan) {
|
|
181098
182193
|
const lines = [];
|
|
@@ -181197,7 +182292,7 @@ function planSkillOperations(input) {
|
|
|
181197
182292
|
if (!availableSkills.has(skillName)) {
|
|
181198
182293
|
continue;
|
|
181199
182294
|
}
|
|
181200
|
-
const destinationDir =
|
|
182295
|
+
const destinationDir = join12(input.projectRoot, destination, skillName);
|
|
181201
182296
|
const path = relative3(input.projectRoot, destinationDir);
|
|
181202
182297
|
const kind = classifyManagedPath(input.projectRoot, path);
|
|
181203
182298
|
if (kind !== "absent" && kind !== "directory") {
|
|
@@ -181236,7 +182331,7 @@ function planLegacySkillOperations(input) {
|
|
|
181236
182331
|
const operations = [];
|
|
181237
182332
|
const leftover = [];
|
|
181238
182333
|
for (const skillName of LEGACY_SKILL_NAMES) {
|
|
181239
|
-
const destinationDir =
|
|
182334
|
+
const destinationDir = join12(input.projectRoot, input.destination, skillName);
|
|
181240
182335
|
const path = relative3(input.projectRoot, destinationDir);
|
|
181241
182336
|
const kind = classifyManagedPath(input.projectRoot, path);
|
|
181242
182337
|
if (kind === "absent") {
|
|
@@ -181316,7 +182411,7 @@ function resolveInitWithAgentTarget(discovery, options) {
|
|
|
181316
182411
|
return { target: discovery.agent.defaultTarget, error: undefined };
|
|
181317
182412
|
}
|
|
181318
182413
|
function readExistingConfig(projectRoot) {
|
|
181319
|
-
const configPath =
|
|
182414
|
+
const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
|
|
181320
182415
|
let rawText;
|
|
181321
182416
|
try {
|
|
181322
182417
|
rawText = readFileSync7(configPath, "utf8");
|
|
@@ -181567,7 +182662,7 @@ function executePlan(projectRoot, plan, packageRoot) {
|
|
|
181567
182662
|
}
|
|
181568
182663
|
}
|
|
181569
182664
|
function executeFileOperation(projectRoot, operation) {
|
|
181570
|
-
const absolutePath =
|
|
182665
|
+
const absolutePath = join13(projectRoot, operation.path);
|
|
181571
182666
|
if (operation.action === "create" && operation.content !== undefined) {
|
|
181572
182667
|
writeFileSync2(absolutePath, operation.content, "utf8");
|
|
181573
182668
|
}
|
|
@@ -181576,10 +182671,10 @@ function executeFileOperation(projectRoot, operation) {
|
|
|
181576
182671
|
}
|
|
181577
182672
|
}
|
|
181578
182673
|
function resolveProjectRoot(root, command) {
|
|
181579
|
-
const projectRoot =
|
|
182674
|
+
const projectRoot = resolve3(root);
|
|
181580
182675
|
let stats;
|
|
181581
182676
|
try {
|
|
181582
|
-
stats =
|
|
182677
|
+
stats = statSync4(projectRoot);
|
|
181583
182678
|
} catch {
|
|
181584
182679
|
throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
|
|
181585
182680
|
}
|
|
@@ -181614,24 +182709,12 @@ ${block.content}`;
|
|
|
181614
182709
|
const fence = codeFence(block.content);
|
|
181615
182710
|
return `${header}
|
|
181616
182711
|
|
|
181617
|
-
${fence}${fenceLanguage(block
|
|
182712
|
+
${fence}${fenceLanguage(block)}
|
|
181618
182713
|
${block.content}
|
|
181619
182714
|
${fence}`;
|
|
181620
182715
|
}
|
|
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";
|
|
182716
|
+
function fenceLanguage(block) {
|
|
182717
|
+
return block.language === undefined ? "ts" : codeFenceLanguage(block.language, block.filePath);
|
|
181635
182718
|
}
|
|
181636
182719
|
function codeFence(content) {
|
|
181637
182720
|
let longestRun = 0;
|
|
@@ -181763,12 +182846,12 @@ function formatRelatedSummary(summary) {
|
|
|
181763
182846
|
}
|
|
181764
182847
|
|
|
181765
182848
|
// src/cli/upgrade.ts
|
|
181766
|
-
import { statSync as
|
|
181767
|
-
import { resolve as
|
|
182849
|
+
import { statSync as statSync5 } from "node:fs";
|
|
182850
|
+
import { resolve as resolve4 } from "node:path";
|
|
181768
182851
|
|
|
181769
182852
|
// src/setup/upgrade-plan.ts
|
|
181770
|
-
import { existsSync as
|
|
181771
|
-
import { join as
|
|
182853
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
182854
|
+
import { join as join14, relative as relative4 } from "node:path";
|
|
181772
182855
|
var MANAGED_SKILL_NAME = INIT_SKILL_NAMES[0];
|
|
181773
182856
|
function reportCliVersion(currentVersion, latest) {
|
|
181774
182857
|
if (latest.status !== "ok") {
|
|
@@ -181786,7 +182869,7 @@ function resolveUpgradeAgentTarget(discovery, explicitTarget) {
|
|
|
181786
182869
|
return explicitTarget ?? discovery.agent.defaultTarget;
|
|
181787
182870
|
}
|
|
181788
182871
|
function inspectManagedSkill(input) {
|
|
181789
|
-
const destinationDir =
|
|
182872
|
+
const destinationDir = join14(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
|
|
181790
182873
|
const path = toRootRelative(input.projectRoot, destinationDir);
|
|
181791
182874
|
const empty = {
|
|
181792
182875
|
destination: input.destination,
|
|
@@ -181802,7 +182885,7 @@ function inspectManagedSkill(input) {
|
|
|
181802
182885
|
if (!input.templateAvailable) {
|
|
181803
182886
|
return { ...empty, state: "template-missing" };
|
|
181804
182887
|
}
|
|
181805
|
-
const comparison = compareSkillTree(destinationDir,
|
|
182888
|
+
const comparison = compareSkillTree(destinationDir, join14(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
|
|
181806
182889
|
const drifted = comparison.modified.length > 0 || comparison.missing.length > 0 || comparison.extra.length > 0;
|
|
181807
182890
|
return {
|
|
181808
182891
|
destination: input.destination,
|
|
@@ -181816,13 +182899,13 @@ function inspectManagedSkill(input) {
|
|
|
181816
182899
|
function inspectLegacySkills(projectRoot, destination) {
|
|
181817
182900
|
const reports = [];
|
|
181818
182901
|
for (const skillName of LEGACY_SKILL_NAMES) {
|
|
181819
|
-
const destinationDir =
|
|
182902
|
+
const destinationDir = join14(projectRoot, destination, skillName);
|
|
181820
182903
|
const path = toRootRelative(projectRoot, destinationDir);
|
|
181821
182904
|
const kind = classifyManagedPath(projectRoot, path);
|
|
181822
182905
|
if (kind === "absent") {
|
|
181823
182906
|
continue;
|
|
181824
182907
|
}
|
|
181825
|
-
if (kind === "symlinked-parent" && !
|
|
182908
|
+
if (kind === "symlinked-parent" && !existsSync6(destinationDir)) {
|
|
181826
182909
|
continue;
|
|
181827
182910
|
}
|
|
181828
182911
|
reports.push({ path, kind });
|
|
@@ -182086,10 +183169,10 @@ function parseAgentTarget(value) {
|
|
|
182086
183169
|
return value;
|
|
182087
183170
|
}
|
|
182088
183171
|
function resolveProjectRoot2(root) {
|
|
182089
|
-
const projectRoot =
|
|
183172
|
+
const projectRoot = resolve4(root);
|
|
182090
183173
|
let stats;
|
|
182091
183174
|
try {
|
|
182092
|
-
stats =
|
|
183175
|
+
stats = statSync5(projectRoot);
|
|
182093
183176
|
} catch {
|
|
182094
183177
|
throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance("upgrade"));
|
|
182095
183178
|
}
|
|
@@ -182114,10 +183197,10 @@ function parseGraphOptions(args) {
|
|
|
182114
183197
|
return parseCommandOptions("graph", args);
|
|
182115
183198
|
}
|
|
182116
183199
|
function resolveProjectRoot3(root, command) {
|
|
182117
|
-
const projectRoot =
|
|
183200
|
+
const projectRoot = resolve5(root);
|
|
182118
183201
|
let stats;
|
|
182119
183202
|
try {
|
|
182120
|
-
stats =
|
|
183203
|
+
stats = statSync6(projectRoot);
|
|
182121
183204
|
} catch {
|
|
182122
183205
|
throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
|
|
182123
183206
|
}
|
|
@@ -182241,7 +183324,7 @@ function repairGuidanceFor(diagnostics, projectRoot) {
|
|
|
182241
183324
|
if (invalid.length === 0) {
|
|
182242
183325
|
return;
|
|
182243
183326
|
}
|
|
182244
|
-
return
|
|
183327
|
+
return existsSync7(join15(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
|
|
182245
183328
|
}
|
|
182246
183329
|
function unknownCommandGuidance(command) {
|
|
182247
183330
|
const suggestion = nearestMatch(command, SUBCOMMANDS);
|
|
@@ -182316,7 +183399,7 @@ function run(argv, io = {
|
|
|
182316
183399
|
function rootOptionOf(argv, currentDirectory) {
|
|
182317
183400
|
const flagIndex = argv.indexOf("--root");
|
|
182318
183401
|
const value = flagIndex === -1 ? undefined : argv[flagIndex + 1];
|
|
182319
|
-
return value === undefined ? currentDirectory :
|
|
183402
|
+
return value === undefined ? currentDirectory : resolve5(currentDirectory, value);
|
|
182320
183403
|
}
|
|
182321
183404
|
function writeUpdateNotice(argv, io, initRuntime, cliRuntime) {
|
|
182322
183405
|
const latest = cliRuntime.latest;
|