docbridge 0.10.1 → 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 +74 -1
- package/README.md +16 -9
- package/dist/bin/darwin-arm64/SHA256SUMS +3 -2
- 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 +3 -2
- package/dist/bin/linux-x64/docbridge-go-scanner +0 -0
- package/dist/bin/linux-x64/docbridge-swift-scanner +1 -1
- package/dist/bin/linux-x64/docbridge_dart_scanner +0 -0
- package/dist/index.js +1773 -668
- 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 +105 -13
- package/docs/user/getting-started.md +1 -1
- package/docs/user/linking.md +105 -10
- package/docs/user/troubleshooting.md +23 -10
- 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 +118 -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 = [];
|
|
@@ -175787,11 +175777,47 @@ var KNOWN_CODE_LANGUAGES = [
|
|
|
175787
175777
|
"typescript",
|
|
175788
175778
|
"swift",
|
|
175789
175779
|
"dart",
|
|
175790
|
-
"rust"
|
|
175780
|
+
"rust",
|
|
175781
|
+
"go",
|
|
175782
|
+
"javascript",
|
|
175783
|
+
"python",
|
|
175784
|
+
"ruby"
|
|
175791
175785
|
];
|
|
175792
175786
|
function isCodeLanguage(value) {
|
|
175793
175787
|
return KNOWN_CODE_LANGUAGES.includes(value);
|
|
175794
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
|
+
}
|
|
175795
175821
|
function collectCodeFiles(projectRoot, codeInclude) {
|
|
175796
175822
|
const collected = [];
|
|
175797
175823
|
const seen = new Set;
|
|
@@ -175800,7 +175826,7 @@ function collectCodeFiles(projectRoot, codeInclude) {
|
|
|
175800
175826
|
if (entry === undefined) {
|
|
175801
175827
|
continue;
|
|
175802
175828
|
}
|
|
175803
|
-
for (const relPath of
|
|
175829
|
+
for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
|
|
175804
175830
|
if (seen.has(relPath)) {
|
|
175805
175831
|
continue;
|
|
175806
175832
|
}
|
|
@@ -175818,7 +175844,7 @@ function codeFileOwners(projectRoot, codeInclude) {
|
|
|
175818
175844
|
if (entry === undefined) {
|
|
175819
175845
|
continue;
|
|
175820
175846
|
}
|
|
175821
|
-
for (const relPath of
|
|
175847
|
+
for (const relPath of collectLanguageFiles(projectRoot, language, entry.patterns)) {
|
|
175822
175848
|
const existing = owners.get(relPath);
|
|
175823
175849
|
if (existing === undefined) {
|
|
175824
175850
|
owners.set(relPath, [language]);
|
|
@@ -175829,26 +175855,76 @@ function codeFileOwners(projectRoot, codeInclude) {
|
|
|
175829
175855
|
}
|
|
175830
175856
|
return owners;
|
|
175831
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
|
+
}
|
|
175832
175910
|
|
|
175833
175911
|
// src/config/config.ts
|
|
175834
175912
|
var CONFIG_FILE_NAME = "docbridge.config.json";
|
|
175835
175913
|
var EMPTY_CONFIG = {
|
|
175836
175914
|
include: { code: {}, docs: [] }
|
|
175837
175915
|
};
|
|
175838
|
-
var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include"]);
|
|
175916
|
+
var KNOWN_TOP_LEVEL_KEYS = new Set(["$schema", "include", "scanners"]);
|
|
175839
175917
|
var KNOWN_INCLUDE_KEYS = new Set(["code", "docs"]);
|
|
175840
175918
|
var KNOWN_CODE_ENTRY_KEYS = new Set(["patterns", "visibility"]);
|
|
175841
|
-
var LANGUAGE_SUFFIX = {
|
|
175842
|
-
typescript: ".ts",
|
|
175843
|
-
swift: ".swift",
|
|
175844
|
-
dart: ".dart",
|
|
175845
|
-
rust: ".rs"
|
|
175846
|
-
};
|
|
175847
175919
|
var LANGUAGE_VISIBILITY = {
|
|
175848
175920
|
typescript: ["public", "protected", "private"],
|
|
175849
175921
|
swift: ["public", "open", "internal"],
|
|
175850
175922
|
dart: ["public"],
|
|
175851
|
-
rust: ["pub", "private"]
|
|
175923
|
+
rust: ["pub", "private"],
|
|
175924
|
+
go: ["exported", "unexported"],
|
|
175925
|
+
javascript: ["public", "protected", "private"],
|
|
175926
|
+
python: ["public", "private"],
|
|
175927
|
+
ruby: ["public", "protected", "private"]
|
|
175852
175928
|
};
|
|
175853
175929
|
function loadConfig(projectRoot) {
|
|
175854
175930
|
let rawText;
|
|
@@ -175877,7 +175953,7 @@ function resolveConfig(rawText) {
|
|
|
175877
175953
|
config: EMPTY_CONFIG,
|
|
175878
175954
|
ok: false,
|
|
175879
175955
|
diagnostics: [
|
|
175880
|
-
|
|
175956
|
+
configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `${CONFIG_FILE_NAME} was not found. DocBridge requires a configuration file.`)
|
|
175881
175957
|
]
|
|
175882
175958
|
};
|
|
175883
175959
|
}
|
|
@@ -175890,57 +175966,61 @@ function resolveConfig(rawText) {
|
|
|
175890
175966
|
config: EMPTY_CONFIG,
|
|
175891
175967
|
ok: false,
|
|
175892
175968
|
diagnostics: [
|
|
175893
|
-
|
|
175969
|
+
configDiagnostic2("config_file_invalid", CONFIG_FILE_NAME, `Failed to parse ${CONFIG_FILE_NAME}: ${reason}`)
|
|
175894
175970
|
]
|
|
175895
175971
|
};
|
|
175896
175972
|
}
|
|
175897
175973
|
const diagnostics = [];
|
|
175898
|
-
if (!
|
|
175899
|
-
diagnostics.push(
|
|
175974
|
+
if (!isPlainObject2(parsed)) {
|
|
175975
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", CONFIG_FILE_NAME, "Configuration must be a JSON object."));
|
|
175900
175976
|
return { config: EMPTY_CONFIG, diagnostics, ok: false };
|
|
175901
175977
|
}
|
|
175902
175978
|
for (const key of Object.keys(parsed)) {
|
|
175903
175979
|
if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
|
|
175904
|
-
diagnostics.push(
|
|
175980
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", key, `Unknown top-level configuration key: ${key}`));
|
|
175905
175981
|
}
|
|
175906
175982
|
}
|
|
175907
175983
|
if ("$schema" in parsed && typeof parsed.$schema !== "string") {
|
|
175908
|
-
diagnostics.push(
|
|
175984
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "$schema", "`$schema` must be a string."));
|
|
175909
175985
|
}
|
|
175986
|
+
const scanners = "scanners" in parsed ? validateScannerRuntimes(parsed.scanners, diagnostics) : undefined;
|
|
175910
175987
|
const include = parsed.include;
|
|
175911
|
-
if (!
|
|
175912
|
-
diagnostics.push(
|
|
175988
|
+
if (!isPlainObject2(include)) {
|
|
175989
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include", "`include` must be an object with `code` and `docs`."));
|
|
175913
175990
|
return { config: EMPTY_CONFIG, diagnostics, ok: false };
|
|
175914
175991
|
}
|
|
175915
175992
|
for (const key of Object.keys(include)) {
|
|
175916
175993
|
if (!KNOWN_INCLUDE_KEYS.has(key)) {
|
|
175917
|
-
diagnostics.push(
|
|
175994
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", `include.${key}`, `Unknown configuration key under \`include\`: ${key}`));
|
|
175918
175995
|
}
|
|
175919
175996
|
}
|
|
175920
175997
|
const code = validateCodeInclude(include.code, diagnostics);
|
|
175921
|
-
validatePatternArray(include.docs, "docs", ".md",
|
|
175998
|
+
validatePatternArray(include.docs, "docs", [".md"], [], diagnostics);
|
|
175922
175999
|
const ok = diagnostics.length === 0;
|
|
175923
|
-
const config = ok ? {
|
|
176000
|
+
const config = ok ? {
|
|
176001
|
+
include: { code, docs: include.docs },
|
|
176002
|
+
...scanners === undefined ? {} : { scanners }
|
|
176003
|
+
} : EMPTY_CONFIG;
|
|
175924
176004
|
return { config, diagnostics, ok };
|
|
175925
176005
|
}
|
|
175926
176006
|
function validateCodeInclude(value, diagnostics) {
|
|
175927
176007
|
if (Array.isArray(value)) {
|
|
175928
|
-
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.'));
|
|
175929
176009
|
return {};
|
|
175930
176010
|
}
|
|
175931
|
-
if (!
|
|
175932
|
-
diagnostics.push(
|
|
176011
|
+
if (!isPlainObject2(value)) {
|
|
176012
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must be a language-keyed object."));
|
|
175933
176013
|
return {};
|
|
175934
176014
|
}
|
|
175935
176015
|
const keys = Object.keys(value);
|
|
175936
176016
|
if (keys.length === 0) {
|
|
175937
|
-
diagnostics.push(
|
|
176017
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", "include.code", "`include.code` must configure at least one language."));
|
|
175938
176018
|
return {};
|
|
175939
176019
|
}
|
|
175940
176020
|
const result = {};
|
|
175941
176021
|
for (const key of keys) {
|
|
175942
176022
|
if (!isCodeLanguage(key)) {
|
|
175943
|
-
diagnostics.push(
|
|
176023
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `include.code.${key}`, `Unknown code language: ${key}. Supported languages: ${KNOWN_CODE_LANGUAGES.join(", ")}.`));
|
|
175944
176024
|
continue;
|
|
175945
176025
|
}
|
|
175946
176026
|
const entry = validateCodeEntry(key, value[key], diagnostics);
|
|
@@ -175952,20 +176032,20 @@ function validateCodeInclude(value, diagnostics) {
|
|
|
175952
176032
|
}
|
|
175953
176033
|
function validateCodeEntry(language, value, diagnostics) {
|
|
175954
176034
|
const target = `include.code.${language}`;
|
|
175955
|
-
if (!
|
|
175956
|
-
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.`));
|
|
175957
176037
|
return;
|
|
175958
176038
|
}
|
|
175959
176039
|
for (const key of Object.keys(value)) {
|
|
175960
176040
|
if (!KNOWN_CODE_ENTRY_KEYS.has(key)) {
|
|
175961
|
-
diagnostics.push(
|
|
176041
|
+
diagnostics.push(configDiagnostic2("config_unknown_key", `${target}.${key}`, `Unknown configuration key under \`${target}\`: ${key}`));
|
|
175962
176042
|
}
|
|
175963
176043
|
}
|
|
175964
176044
|
const before = diagnostics.length;
|
|
175965
|
-
validatePatternArray(value.patterns, `code.${language}.patterns`,
|
|
176045
|
+
validatePatternArray(value.patterns, `code.${language}.patterns`, LANGUAGE_SUFFIXES[language], EXCLUDED_SUFFIXES[language], diagnostics);
|
|
175966
176046
|
if ("visibility" in value) {
|
|
175967
176047
|
if (!isStringArray(value.visibility)) {
|
|
175968
|
-
diagnostics.push(
|
|
176048
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` must be an array of strings.`));
|
|
175969
176049
|
} else {
|
|
175970
176050
|
validateVisibilityOptions(language, value.visibility, target, diagnostics);
|
|
175971
176051
|
}
|
|
@@ -175982,47 +176062,49 @@ function validateCodeEntry(language, value, diagnostics) {
|
|
|
175982
176062
|
function validateVisibilityOptions(language, values, target, diagnostics) {
|
|
175983
176063
|
const allowed = LANGUAGE_VISIBILITY[language];
|
|
175984
176064
|
if (allowed.length === 0) {
|
|
175985
|
-
diagnostics.push(
|
|
176065
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `\`${target}.visibility\` is not supported for ${language}.`));
|
|
175986
176066
|
return;
|
|
175987
176067
|
}
|
|
175988
176068
|
for (const value of values) {
|
|
175989
176069
|
if (!allowed.includes(value)) {
|
|
175990
|
-
diagnostics.push(
|
|
176070
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", `${target}.visibility`, `Unsupported ${language} visibility: ${value}. Supported values: ${allowed.join(", ")}.`));
|
|
175991
176071
|
}
|
|
175992
176072
|
}
|
|
175993
176073
|
}
|
|
175994
|
-
function validatePatternArray(value, field,
|
|
176074
|
+
function validatePatternArray(value, field, suffixes, excludedSuffixes, diagnostics) {
|
|
175995
176075
|
const target = `include.${field}`;
|
|
175996
176076
|
if (!Array.isArray(value)) {
|
|
175997
|
-
diagnostics.push(
|
|
176077
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must be a non-empty array of strings.`));
|
|
175998
176078
|
return;
|
|
175999
176079
|
}
|
|
176000
176080
|
if (value.length === 0) {
|
|
176001
|
-
diagnostics.push(
|
|
176081
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` must contain at least one pattern.`));
|
|
176002
176082
|
return;
|
|
176003
176083
|
}
|
|
176004
176084
|
for (const pattern of value) {
|
|
176005
176085
|
if (typeof pattern !== "string") {
|
|
176006
|
-
diagnostics.push(
|
|
176086
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", target, `\`${target}\` patterns must be strings.`));
|
|
176007
176087
|
continue;
|
|
176008
176088
|
}
|
|
176009
176089
|
const globResult = validateGlobPattern(pattern);
|
|
176010
176090
|
if (!globResult.ok) {
|
|
176011
|
-
diagnostics.push(
|
|
176091
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", pattern, `Invalid pattern in \`${target}\`: ${globResult.message}`));
|
|
176012
176092
|
continue;
|
|
176013
176093
|
}
|
|
176014
|
-
const suffixError =
|
|
176094
|
+
const suffixError = checkPatternSuffix(pattern, suffixes, excludedSuffixes);
|
|
176015
176095
|
if (suffixError !== undefined) {
|
|
176016
|
-
diagnostics.push(
|
|
176096
|
+
diagnostics.push(configDiagnostic2("config_invalid_value", pattern, suffixError));
|
|
176017
176097
|
}
|
|
176018
176098
|
}
|
|
176019
176099
|
}
|
|
176020
|
-
function
|
|
176021
|
-
if (!pattern.endsWith(
|
|
176022
|
-
|
|
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}.`;
|
|
176023
176104
|
}
|
|
176024
|
-
|
|
176025
|
-
|
|
176105
|
+
const excluded = excludedSuffixes.find((suffix) => pattern.endsWith(suffix));
|
|
176106
|
+
if (excluded !== undefined) {
|
|
176107
|
+
return `Pattern must not target \`${excluded}\` declaration files.`;
|
|
176026
176108
|
}
|
|
176027
176109
|
return;
|
|
176028
176110
|
}
|
|
@@ -176030,15 +176112,15 @@ function detectLanguageOverlap(projectRoot, code) {
|
|
|
176030
176112
|
const diagnostics = [];
|
|
176031
176113
|
for (const [relPath, owners] of codeFileOwners(projectRoot, code)) {
|
|
176032
176114
|
if (owners.length > 1) {
|
|
176033
|
-
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.`));
|
|
176034
176116
|
}
|
|
176035
176117
|
}
|
|
176036
176118
|
return diagnostics;
|
|
176037
176119
|
}
|
|
176038
|
-
function
|
|
176120
|
+
function configDiagnostic2(code, target, message) {
|
|
176039
176121
|
return { severity: "error", code, target, message };
|
|
176040
176122
|
}
|
|
176041
|
-
function
|
|
176123
|
+
function isPlainObject2(value) {
|
|
176042
176124
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
176043
176125
|
}
|
|
176044
176126
|
function isStringArray(value) {
|
|
@@ -176565,6 +176647,11 @@ function positionOf(range) {
|
|
|
176565
176647
|
// src/lsp/server.ts
|
|
176566
176648
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
176567
176649
|
|
|
176650
|
+
// src/shared/error.ts
|
|
176651
|
+
function reasonOf(error) {
|
|
176652
|
+
return error instanceof Error ? error.message : String(error);
|
|
176653
|
+
}
|
|
176654
|
+
|
|
176568
176655
|
// src/lsp/position.ts
|
|
176569
176656
|
function toLspPosition(position) {
|
|
176570
176657
|
return { line: position.line - 1, character: position.column - 1 };
|
|
@@ -176948,31 +177035,27 @@ function renderCodeSignatures(state, counterparts) {
|
|
|
176948
177035
|
const signature = content === undefined || symbol.signatureRange === undefined ? "" : withoutLeadingComments(sliceSourceRange(content, symbol.signatureRange).content, symbol.language).trimEnd();
|
|
176949
177036
|
const fenced = signature.length > 0 ? `
|
|
176950
177037
|
|
|
176951
|
-
\`\`\`${
|
|
177038
|
+
\`\`\`${codeFenceLanguage(symbol.language, symbol.filePath)}
|
|
176952
177039
|
${signature}
|
|
176953
177040
|
\`\`\`` : "";
|
|
176954
177041
|
blocks.push(`**${symbol.endpoint}**${fenced}`);
|
|
176955
177042
|
}
|
|
176956
177043
|
return blocks.length > 0 ? blocks.join(DIVIDER) : null;
|
|
176957
177044
|
}
|
|
176958
|
-
var FENCE_LANGUAGE = {
|
|
176959
|
-
typescript: "ts",
|
|
176960
|
-
swift: "swift",
|
|
176961
|
-
dart: "dart",
|
|
176962
|
-
rust: "rust"
|
|
176963
|
-
};
|
|
176964
177045
|
var NESTED_BLOCK_COMMENTS = new Set(["swift", "dart", "rust"]);
|
|
177046
|
+
var HASH_LINE_COMMENTS = new Set(["python", "ruby"]);
|
|
176965
177047
|
function withoutLeadingComments(text, language) {
|
|
177048
|
+
const lineComment = HASH_LINE_COMMENTS.has(language) ? "#" : "//";
|
|
176966
177049
|
let index = 0;
|
|
176967
177050
|
for (;; ) {
|
|
176968
177051
|
while (index < text.length && /\s/.test(text[index] ?? "")) {
|
|
176969
177052
|
index += 1;
|
|
176970
177053
|
}
|
|
176971
|
-
if (text.startsWith(
|
|
177054
|
+
if (text.startsWith(lineComment, index)) {
|
|
176972
177055
|
const newline = text.indexOf(`
|
|
176973
177056
|
`, index);
|
|
176974
177057
|
index = newline === -1 ? text.length : newline + 1;
|
|
176975
|
-
} else if (text.startsWith("/*", index)) {
|
|
177058
|
+
} else if (lineComment === "//" && text.startsWith("/*", index)) {
|
|
176976
177059
|
index = blockCommentEnd(text, index, NESTED_BLOCK_COMMENTS.has(language));
|
|
176977
177060
|
} else {
|
|
176978
177061
|
return text.slice(index);
|
|
@@ -177513,24 +177596,205 @@ function manifestLink(source, target, entry, side) {
|
|
|
177513
177596
|
};
|
|
177514
177597
|
}
|
|
177515
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
|
+
|
|
177516
177700
|
// src/scan/code/typescript.ts
|
|
177701
|
+
var import_typescript2 = __toESM(require_typescript(), 1);
|
|
177702
|
+
|
|
177703
|
+
// src/scan/code/typescript-diagnostics.ts
|
|
177517
177704
|
var import_typescript = __toESM(require_typescript(), 1);
|
|
177518
|
-
|
|
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
|
|
177519
177776
|
var typeScriptAdapter = {
|
|
177520
|
-
language:
|
|
177777
|
+
language: "typescript",
|
|
177521
177778
|
scanFile(filePath, content, options) {
|
|
177522
177779
|
return scanTypeScript(filePath, content, options);
|
|
177780
|
+
},
|
|
177781
|
+
scanFiles(files, options) {
|
|
177782
|
+
return files.map(({ filePath, content }) => scanTypeScript(filePath, content, options));
|
|
177523
177783
|
}
|
|
177524
177784
|
};
|
|
177785
|
+
var javaScriptAdapter = {
|
|
177786
|
+
...typeScriptAdapter,
|
|
177787
|
+
language: "javascript"
|
|
177788
|
+
};
|
|
177525
177789
|
var DEFAULT_MEMBER_VISIBILITY = ["public", "protected"];
|
|
177526
177790
|
function scanTypeScript(filePath, content, options = {}) {
|
|
177527
177791
|
const visibility = new Set(options.visibility ?? DEFAULT_MEMBER_VISIBILITY);
|
|
177528
|
-
const sourceFile =
|
|
177529
|
-
const
|
|
177530
|
-
if (
|
|
177531
|
-
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];
|
|
177532
177796
|
return {
|
|
177533
|
-
language:
|
|
177797
|
+
language: scriptLanguage(filePath),
|
|
177534
177798
|
filePath,
|
|
177535
177799
|
symbols: [],
|
|
177536
177800
|
undocumentedSymbols: [],
|
|
@@ -177600,11 +177864,13 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177600
177864
|
}
|
|
177601
177865
|
const parsed = parseLinkTarget(docTag.rawTarget, parseOptions);
|
|
177602
177866
|
if (!parsed.ok) {
|
|
177603
|
-
diagnostics.push(parsed.diagnostic);
|
|
177867
|
+
diagnostics.push(linkDiagnostic(filePath, parsed.diagnostic));
|
|
177604
177868
|
continue;
|
|
177605
177869
|
}
|
|
177606
177870
|
if (linkTargetsSeen.has(docTag.rawTarget)) {
|
|
177607
|
-
|
|
177871
|
+
const { rawTarget, location, targetRange } = docTag;
|
|
177872
|
+
const duplicate = duplicateLinkDiagnostic(endpoint, rawTarget, location, targetRange);
|
|
177873
|
+
diagnostics.push(linkDiagnostic(filePath, duplicate));
|
|
177608
177874
|
continue;
|
|
177609
177875
|
}
|
|
177610
177876
|
linkTargetsSeen.add(docTag.rawTarget);
|
|
@@ -177620,7 +177886,7 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177620
177886
|
}
|
|
177621
177887
|
}
|
|
177622
177888
|
return {
|
|
177623
|
-
language:
|
|
177889
|
+
language: scriptLanguage(filePath),
|
|
177624
177890
|
filePath,
|
|
177625
177891
|
symbols,
|
|
177626
177892
|
undocumentedSymbols,
|
|
@@ -177628,15 +177894,60 @@ function scanTypeScript(filePath, content, options = {}) {
|
|
|
177628
177894
|
diagnostics
|
|
177629
177895
|
};
|
|
177630
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
|
+
}
|
|
177631
177917
|
function getParseDiagnostics(sourceFile) {
|
|
177632
177918
|
const withDiagnostics = sourceFile;
|
|
177633
177919
|
return withDiagnostics.parseDiagnostics ?? [];
|
|
177634
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
|
+
}
|
|
177635
177946
|
function collectDocTags(filePath, sourceFile, statement) {
|
|
177636
177947
|
const node = jsDocCarrier(statement);
|
|
177637
177948
|
const location = locationOf(filePath, sourceFile, statement);
|
|
177638
177949
|
const tags = [];
|
|
177639
|
-
for (const tag of
|
|
177950
|
+
for (const tag of import_typescript2.default.getJSDocTags(node)) {
|
|
177640
177951
|
if (tag.tagName.escapedText !== "doc") {
|
|
177641
177952
|
continue;
|
|
177642
177953
|
}
|
|
@@ -177655,7 +177966,7 @@ function collectDocTags(filePath, sourceFile, statement) {
|
|
|
177655
177966
|
return tags;
|
|
177656
177967
|
}
|
|
177657
177968
|
function jsDocCarrier(statement) {
|
|
177658
|
-
if (
|
|
177969
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177659
177970
|
const declaration = statement.declarationList.declarations[0];
|
|
177660
177971
|
if (declaration !== undefined) {
|
|
177661
177972
|
return declaration;
|
|
@@ -177701,11 +178012,11 @@ function collectMemberDeclarations(filePath, sourceFile, statement, visibility,
|
|
|
177701
178012
|
}
|
|
177702
178013
|
}
|
|
177703
178014
|
function diagnoseParameterProperties(filePath, sourceFile, member, diagnostics) {
|
|
177704
|
-
if (!
|
|
178015
|
+
if (!import_typescript2.default.isConstructorDeclaration(member)) {
|
|
177705
178016
|
return;
|
|
177706
178017
|
}
|
|
177707
178018
|
for (const parameter of member.parameters) {
|
|
177708
|
-
if (!
|
|
178019
|
+
if (!import_typescript2.default.isParameterPropertyDeclaration(parameter, member)) {
|
|
177709
178020
|
continue;
|
|
177710
178021
|
}
|
|
177711
178022
|
if (collectDocTags(filePath, sourceFile, parameter).length > 0) {
|
|
@@ -177721,22 +178032,22 @@ function describeContainer(statement) {
|
|
|
177721
178032
|
return { name: supportedNameNode(statement)?.text ?? null, members };
|
|
177722
178033
|
}
|
|
177723
178034
|
function containerMembers(statement) {
|
|
177724
|
-
if (
|
|
178035
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177725
178036
|
return statement.members;
|
|
177726
178037
|
}
|
|
177727
|
-
if (
|
|
178038
|
+
if (import_typescript2.default.isInterfaceDeclaration(statement)) {
|
|
177728
178039
|
return statement.members;
|
|
177729
178040
|
}
|
|
177730
|
-
if (
|
|
178041
|
+
if (import_typescript2.default.isEnumDeclaration(statement)) {
|
|
177731
178042
|
return statement.members;
|
|
177732
178043
|
}
|
|
177733
|
-
if (
|
|
177734
|
-
return
|
|
178044
|
+
if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
|
|
178045
|
+
return import_typescript2.default.isTypeLiteralNode(statement.type) ? statement.type.members : null;
|
|
177735
178046
|
}
|
|
177736
|
-
if (
|
|
178047
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177737
178048
|
const declarations = statement.declarationList.declarations;
|
|
177738
178049
|
const initializer = declarations.length === 1 ? declarations[0]?.initializer : undefined;
|
|
177739
|
-
return initializer !== undefined &&
|
|
178050
|
+
return initializer !== undefined && import_typescript2.default.isClassExpression(initializer) ? initializer.members : null;
|
|
177740
178051
|
}
|
|
177741
178052
|
return null;
|
|
177742
178053
|
}
|
|
@@ -177758,24 +178069,24 @@ function describeMember(filePath, sourceFile, containerName, member, docTags) {
|
|
|
177758
178069
|
};
|
|
177759
178070
|
}
|
|
177760
178071
|
function isVisibleMember(member, visibility) {
|
|
177761
|
-
if (hasModifier(member,
|
|
178072
|
+
if (hasModifier(member, import_typescript2.default.SyntaxKind.PrivateKeyword)) {
|
|
177762
178073
|
return visibility.has("private");
|
|
177763
178074
|
}
|
|
177764
|
-
if (hasModifier(member,
|
|
178075
|
+
if (hasModifier(member, import_typescript2.default.SyntaxKind.ProtectedKeyword)) {
|
|
177765
178076
|
return visibility.has("protected");
|
|
177766
178077
|
}
|
|
177767
178078
|
return visibility.has("public");
|
|
177768
178079
|
}
|
|
177769
178080
|
function memberIdentity(sourceFile, member) {
|
|
177770
|
-
if (
|
|
177771
|
-
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);
|
|
177772
178083
|
return keyword === undefined ? null : { name: "constructor", nameRange: rangeOfNode(sourceFile, keyword) };
|
|
177773
178084
|
}
|
|
177774
|
-
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)) {
|
|
177775
178086
|
return null;
|
|
177776
178087
|
}
|
|
177777
178088
|
const name = member.name;
|
|
177778
|
-
return
|
|
178089
|
+
return import_typescript2.default.isIdentifier(name) ? { name: name.text, nameRange: rangeOfNode(sourceFile, name) } : null;
|
|
177779
178090
|
}
|
|
177780
178091
|
function memberSignatureEndOffset(sourceFile, member) {
|
|
177781
178092
|
const body = member.body;
|
|
@@ -177784,28 +178095,28 @@ function memberSignatureEndOffset(sourceFile, member) {
|
|
|
177784
178095
|
function supportedNameNode(statement) {
|
|
177785
178096
|
const isExported = hasExportModifier(statement);
|
|
177786
178097
|
const isDefault = hasDefaultModifier(statement);
|
|
177787
|
-
if (
|
|
178098
|
+
if (import_typescript2.default.isFunctionDeclaration(statement)) {
|
|
177788
178099
|
if (!isExported || statement.name === undefined) {
|
|
177789
178100
|
return null;
|
|
177790
178101
|
}
|
|
177791
178102
|
return statement.name;
|
|
177792
178103
|
}
|
|
177793
|
-
if (
|
|
178104
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177794
178105
|
if (!isExported || statement.name === undefined) {
|
|
177795
178106
|
return null;
|
|
177796
178107
|
}
|
|
177797
178108
|
return statement.name;
|
|
177798
178109
|
}
|
|
177799
|
-
if (
|
|
178110
|
+
if (import_typescript2.default.isInterfaceDeclaration(statement)) {
|
|
177800
178111
|
return isExported && !isDefault ? statement.name : null;
|
|
177801
178112
|
}
|
|
177802
|
-
if (
|
|
178113
|
+
if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
|
|
177803
178114
|
return isExported && !isDefault ? statement.name : null;
|
|
177804
178115
|
}
|
|
177805
|
-
if (
|
|
178116
|
+
if (import_typescript2.default.isEnumDeclaration(statement)) {
|
|
177806
178117
|
return isExported && !isDefault ? statement.name : null;
|
|
177807
178118
|
}
|
|
177808
|
-
if (
|
|
178119
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177809
178120
|
if (!isExported) {
|
|
177810
178121
|
return null;
|
|
177811
178122
|
}
|
|
@@ -177815,23 +178126,23 @@ function supportedNameNode(statement) {
|
|
|
177815
178126
|
return null;
|
|
177816
178127
|
}
|
|
177817
178128
|
const name = declaration.name;
|
|
177818
|
-
return
|
|
178129
|
+
return import_typescript2.default.isIdentifier(name) ? name : null;
|
|
177819
178130
|
}
|
|
177820
178131
|
return null;
|
|
177821
178132
|
}
|
|
177822
178133
|
function hasExportModifier(statement) {
|
|
177823
|
-
return hasModifier(statement,
|
|
178134
|
+
return hasModifier(statement, import_typescript2.default.SyntaxKind.ExportKeyword);
|
|
177824
178135
|
}
|
|
177825
178136
|
function hasDefaultModifier(statement) {
|
|
177826
|
-
return hasModifier(statement,
|
|
178137
|
+
return hasModifier(statement, import_typescript2.default.SyntaxKind.DefaultKeyword);
|
|
177827
178138
|
}
|
|
177828
178139
|
function hasModifier(node, kind) {
|
|
177829
|
-
const modifiers =
|
|
178140
|
+
const modifiers = import_typescript2.default.canHaveModifiers(node) ? import_typescript2.default.getModifiers(node) : undefined;
|
|
177830
178141
|
return modifiers?.some((modifier) => modifier.kind === kind) ?? false;
|
|
177831
178142
|
}
|
|
177832
178143
|
function declarationLocation(filePath, sourceFile, node) {
|
|
177833
|
-
const carrier =
|
|
177834
|
-
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);
|
|
177835
178146
|
return locationOf(filePath, sourceFile, name ?? node);
|
|
177836
178147
|
}
|
|
177837
178148
|
function locationOf(filePath, sourceFile, node) {
|
|
@@ -177850,13 +178161,13 @@ function rangeOfNode(sourceFile, node) {
|
|
|
177850
178161
|
return rangeFromOffsets(sourceFile, node.getStart(sourceFile), node.getEnd());
|
|
177851
178162
|
}
|
|
177852
178163
|
function signatureEndOffset(sourceFile, statement) {
|
|
177853
|
-
if (
|
|
178164
|
+
if (import_typescript2.default.isFunctionDeclaration(statement) && statement.body !== undefined) {
|
|
177854
178165
|
return statement.body.getStart(sourceFile);
|
|
177855
178166
|
}
|
|
177856
|
-
if (
|
|
178167
|
+
if (import_typescript2.default.isClassDeclaration(statement)) {
|
|
177857
178168
|
return classBodyStartOffset(sourceFile, statement) ?? statement.getEnd();
|
|
177858
178169
|
}
|
|
177859
|
-
if (
|
|
178170
|
+
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
177860
178171
|
return variableSignatureEndOffset(sourceFile, statement) ?? statement.getEnd();
|
|
177861
178172
|
}
|
|
177862
178173
|
return statement.getEnd();
|
|
@@ -177872,13 +178183,13 @@ function variableSignatureEndOffset(sourceFile, statement) {
|
|
|
177872
178183
|
if (initializer === undefined) {
|
|
177873
178184
|
return;
|
|
177874
178185
|
}
|
|
177875
|
-
if ((
|
|
178186
|
+
if ((import_typescript2.default.isArrowFunction(initializer) || import_typescript2.default.isFunctionExpression(initializer)) && import_typescript2.default.isBlock(initializer.body)) {
|
|
177876
178187
|
return initializer.body.getStart(sourceFile);
|
|
177877
178188
|
}
|
|
177878
|
-
if (
|
|
178189
|
+
if (import_typescript2.default.isObjectLiteralExpression(initializer)) {
|
|
177879
178190
|
return initializer.getStart(sourceFile);
|
|
177880
178191
|
}
|
|
177881
|
-
if (
|
|
178192
|
+
if (import_typescript2.default.isClassExpression(initializer)) {
|
|
177882
178193
|
return classBodyStartOffset(sourceFile, initializer);
|
|
177883
178194
|
}
|
|
177884
178195
|
return;
|
|
@@ -177893,7 +178204,7 @@ function targetRangeOf(sourceFile, tag, rawTarget) {
|
|
|
177893
178204
|
function makeCodeSymbol(filePath, endpoint, declaration) {
|
|
177894
178205
|
const symbol = {
|
|
177895
178206
|
kind: "code",
|
|
177896
|
-
language:
|
|
178207
|
+
language: scriptLanguage(filePath),
|
|
177897
178208
|
filePath,
|
|
177898
178209
|
symbolName: declaration.symbolName,
|
|
177899
178210
|
canonicalId: declaration.canonicalId,
|
|
@@ -177930,217 +178241,20 @@ function firstToken(text) {
|
|
|
177930
178241
|
}
|
|
177931
178242
|
return trimmed.split(/\s+/)[0];
|
|
177932
178243
|
}
|
|
177933
|
-
function parseErrorDiagnostic(filePath, sourceFile, diagnostic) {
|
|
177934
|
-
const location = { filePath, line: 1, column: 1 };
|
|
177935
|
-
if (diagnostic !== undefined && diagnostic.start !== undefined && diagnostic.file !== undefined) {
|
|
177936
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
|
|
177937
|
-
location.line = line + 1;
|
|
177938
|
-
location.column = character + 1;
|
|
177939
|
-
}
|
|
177940
|
-
const detail = diagnostic === undefined ? "TypeScript file has a syntactic parse error." : import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
177941
|
-
`);
|
|
177942
|
-
return {
|
|
177943
|
-
severity: "error",
|
|
177944
|
-
code: "code_parse_error",
|
|
177945
|
-
language: LANGUAGE,
|
|
177946
|
-
target: filePath,
|
|
177947
|
-
message: `TypeScript parse error: ${detail}`,
|
|
177948
|
-
location
|
|
177949
|
-
};
|
|
177950
|
-
}
|
|
177951
|
-
function unsupportedDeclarationDiagnostic(filePath, location) {
|
|
177952
|
-
return {
|
|
177953
|
-
severity: "warning",
|
|
177954
|
-
code: "unsupported_declaration",
|
|
177955
|
-
language: LANGUAGE,
|
|
177956
|
-
target: filePath,
|
|
177957
|
-
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.",
|
|
177958
|
-
location
|
|
177959
|
-
};
|
|
177960
|
-
}
|
|
177961
|
-
function duplicateCodeSymbolDiagnostic(endpoint, location, range) {
|
|
177962
|
-
const diagnostic = {
|
|
177963
|
-
severity: "error",
|
|
177964
|
-
code: "duplicate_code_symbol",
|
|
177965
|
-
language: LANGUAGE,
|
|
177966
|
-
target: endpoint,
|
|
177967
|
-
message: `Multiple @doc-annotated declarations expose the same code endpoint ${endpoint}.`,
|
|
177968
|
-
location
|
|
177969
|
-
};
|
|
177970
|
-
if (range !== undefined) {
|
|
177971
|
-
diagnostic.range = range;
|
|
177972
|
-
}
|
|
177973
|
-
return diagnostic;
|
|
177974
|
-
}
|
|
177975
|
-
function duplicateLinkDiagnostic(source, target, location, range) {
|
|
177976
|
-
const diagnostic = {
|
|
177977
|
-
severity: "warning",
|
|
177978
|
-
code: "duplicate_link",
|
|
177979
|
-
target,
|
|
177980
|
-
source,
|
|
177981
|
-
message: `Duplicate @doc link from ${source} to ${target}.`,
|
|
177982
|
-
location
|
|
177983
|
-
};
|
|
177984
|
-
if (range !== undefined) {
|
|
177985
|
-
diagnostic.range = range;
|
|
177986
|
-
}
|
|
177987
|
-
return diagnostic;
|
|
177988
|
-
}
|
|
177989
178244
|
|
|
177990
|
-
// src/scan/code/worker/
|
|
177991
|
-
import { accessSync
|
|
177992
|
-
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";
|
|
177993
178248
|
|
|
177994
|
-
// src/
|
|
177995
|
-
|
|
177996
|
-
return error instanceof Error ? error.message : String(error);
|
|
177997
|
-
}
|
|
178249
|
+
// src/scan/code/worker/runtime-probe.ts
|
|
178250
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
177998
178251
|
|
|
177999
|
-
// src/
|
|
178000
|
-
|
|
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";
|
|
178001
178257
|
import { dirname, join as join4 } from "node:path";
|
|
178002
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
178003
|
-
function resolvePackageRoot(moduleUrl = import.meta.url) {
|
|
178004
|
-
const modulePath = fileURLToPath2(moduleUrl);
|
|
178005
|
-
let resolved;
|
|
178006
|
-
try {
|
|
178007
|
-
resolved = realpathSync(modulePath);
|
|
178008
|
-
} catch {
|
|
178009
|
-
resolved = modulePath;
|
|
178010
|
-
}
|
|
178011
|
-
let dir = dirname(resolved);
|
|
178012
|
-
for (;; ) {
|
|
178013
|
-
if (existsSync(join4(dir, "templates", "skills"))) {
|
|
178014
|
-
return dir;
|
|
178015
|
-
}
|
|
178016
|
-
const parent = dirname(dir);
|
|
178017
|
-
if (parent === dir) {
|
|
178018
|
-
return dir;
|
|
178019
|
-
}
|
|
178020
|
-
dir = parent;
|
|
178021
|
-
}
|
|
178022
|
-
}
|
|
178023
|
-
|
|
178024
|
-
// src/scan/code/worker/scanner-executable.ts
|
|
178025
|
-
var SUPPORTED_SCANNER_PLATFORM_KEYS = ["darwin-arm64", "linux-x64"];
|
|
178026
|
-
var SCANNER_EXECUTABLE_NAMES = {
|
|
178027
|
-
swift: "docbridge-swift-scanner",
|
|
178028
|
-
dart: "docbridge_dart_scanner",
|
|
178029
|
-
rust: "docbridge-rust-scanner"
|
|
178030
|
-
};
|
|
178031
|
-
function scannerPlatformKey() {
|
|
178032
|
-
return `${process.platform}-${process.arch}`;
|
|
178033
|
-
}
|
|
178034
|
-
function scannerExecutableName(language) {
|
|
178035
|
-
return SCANNER_EXECUTABLE_NAMES[language];
|
|
178036
|
-
}
|
|
178037
|
-
function resolveScannerWorkerCommand(language, options = {}) {
|
|
178038
|
-
const platformKey = options.platformKey ?? scannerPlatformKey();
|
|
178039
|
-
const platformSupported = isSupportedScannerPlatformKey(platformKey);
|
|
178040
|
-
const candidates = scannerExecutableCandidates(language, platformKey, platformSupported, options);
|
|
178041
|
-
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
178042
|
-
if (found !== undefined) {
|
|
178043
|
-
const repair = ensureExecutable(found, options.chmod ?? chmodSync);
|
|
178044
|
-
if (!repair.ok) {
|
|
178045
|
-
return { ok: false, diagnostic: scannerUnavailableDiagnostic(language, repair.reason) };
|
|
178046
|
-
}
|
|
178047
|
-
return { ok: true, command: [found] };
|
|
178048
|
-
}
|
|
178049
|
-
if (!platformSupported) {
|
|
178050
|
-
return {
|
|
178051
|
-
ok: false,
|
|
178052
|
-
diagnostic: scannerUnavailableDiagnostic(language, `platform ${platformKey} is unsupported; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
178053
|
-
};
|
|
178054
|
-
}
|
|
178055
|
-
return {
|
|
178056
|
-
ok: false,
|
|
178057
|
-
diagnostic: scannerUnavailableDiagnostic(language, `missing ${scannerExecutableName(language)} for platform ${platformKey}; supported platforms: ${SUPPORTED_SCANNER_PLATFORM_KEYS.join(", ")}`)
|
|
178058
|
-
};
|
|
178059
|
-
}
|
|
178060
|
-
function scannerExecutableCandidates(language, platformKey, platformSupported, options) {
|
|
178061
|
-
const sourceRoot = options.sourceRoot ?? sourceRootPath();
|
|
178062
|
-
const distRoot = options.distRoot ?? distRootPath();
|
|
178063
|
-
const executable = scannerExecutableName(language);
|
|
178064
|
-
if (language === "swift") {
|
|
178065
|
-
return [
|
|
178066
|
-
join5(sourceRoot, "packages/swift-scanner/.build/release", executable),
|
|
178067
|
-
join5(sourceRoot, "packages/swift-scanner/.build/debug", executable),
|
|
178068
|
-
...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
|
|
178069
|
-
];
|
|
178070
|
-
}
|
|
178071
|
-
if (language === "rust") {
|
|
178072
|
-
return [
|
|
178073
|
-
join5(sourceRoot, "packages/rust-scanner/target/release", executable),
|
|
178074
|
-
join5(sourceRoot, "packages/rust-scanner/target/debug", executable),
|
|
178075
|
-
...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
|
|
178076
|
-
];
|
|
178077
|
-
}
|
|
178078
|
-
return [
|
|
178079
|
-
join5(sourceRoot, "packages/dart-scanner/bin", executable),
|
|
178080
|
-
...platformSupported ? [join5(distRoot, "bin", platformKey, executable)] : []
|
|
178081
|
-
];
|
|
178082
|
-
}
|
|
178083
|
-
function ensureExecutable(path, chmod) {
|
|
178084
|
-
let mode;
|
|
178085
|
-
try {
|
|
178086
|
-
mode = statSync(path).mode;
|
|
178087
|
-
} catch (error) {
|
|
178088
|
-
return { ok: false, reason: `cannot stat ${path}: ${reasonOf(error)}` };
|
|
178089
|
-
}
|
|
178090
|
-
if (isExecutableByThisProcess(path)) {
|
|
178091
|
-
return { ok: true };
|
|
178092
|
-
}
|
|
178093
|
-
try {
|
|
178094
|
-
chmod(path, mode | 73);
|
|
178095
|
-
} catch (error) {
|
|
178096
|
-
return {
|
|
178097
|
-
ok: false,
|
|
178098
|
-
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`
|
|
178099
|
-
};
|
|
178100
|
-
}
|
|
178101
|
-
return { ok: true };
|
|
178102
|
-
}
|
|
178103
|
-
function isExecutableByThisProcess(path) {
|
|
178104
|
-
try {
|
|
178105
|
-
accessSync(path, constants.X_OK);
|
|
178106
|
-
return true;
|
|
178107
|
-
} catch {
|
|
178108
|
-
return false;
|
|
178109
|
-
}
|
|
178110
|
-
}
|
|
178111
|
-
function formatMode(mode) {
|
|
178112
|
-
return `0${(mode & 4095).toString(8).padStart(3, "0")}`;
|
|
178113
|
-
}
|
|
178114
|
-
function isSupportedScannerPlatformKey(platformKey) {
|
|
178115
|
-
return SUPPORTED_SCANNER_PLATFORM_KEYS.includes(platformKey);
|
|
178116
|
-
}
|
|
178117
|
-
function scannerRootsFromModuleUrl(moduleUrl) {
|
|
178118
|
-
const packageRoot = resolvePackageRoot(moduleUrl);
|
|
178119
|
-
return { distRoot: join5(packageRoot, "dist"), sourceRoot: packageRoot };
|
|
178120
|
-
}
|
|
178121
|
-
function sourceRootPath() {
|
|
178122
|
-
return scannerRootsFromModuleUrl(import.meta.url).sourceRoot;
|
|
178123
|
-
}
|
|
178124
|
-
function distRootPath() {
|
|
178125
|
-
return scannerRootsFromModuleUrl(import.meta.url).distRoot;
|
|
178126
|
-
}
|
|
178127
|
-
function scannerUnavailableDiagnostic(language, reason) {
|
|
178128
|
-
const label = language.charAt(0).toUpperCase() + language.slice(1);
|
|
178129
|
-
return {
|
|
178130
|
-
severity: "error",
|
|
178131
|
-
code: "code_scanner_unavailable",
|
|
178132
|
-
language,
|
|
178133
|
-
target: language,
|
|
178134
|
-
message: `${label} scanner worker is unavailable: ${reason}`
|
|
178135
|
-
};
|
|
178136
|
-
}
|
|
178137
|
-
|
|
178138
|
-
// src/scan/code/worker/scanner-worker.ts
|
|
178139
|
-
var import__2020 = __toESM(require_2020(), 1);
|
|
178140
|
-
import { spawnSync } from "node:child_process";
|
|
178141
|
-
import { mkdirSync } from "node:fs";
|
|
178142
|
-
import { tmpdir } from "node:os";
|
|
178143
|
-
import { dirname as dirname2, join as join6 } from "node:path";
|
|
178144
178258
|
// schemas/common-output.schema.json
|
|
178145
178259
|
var common_output_schema_default = {
|
|
178146
178260
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -178188,7 +178302,17 @@ var common_output_schema_default = {
|
|
|
178188
178302
|
target: { type: "string" },
|
|
178189
178303
|
language: {
|
|
178190
178304
|
type: "string",
|
|
178191
|
-
enum: [
|
|
178305
|
+
enum: [
|
|
178306
|
+
"typescript",
|
|
178307
|
+
"swift",
|
|
178308
|
+
"dart",
|
|
178309
|
+
"rust",
|
|
178310
|
+
"go",
|
|
178311
|
+
"javascript",
|
|
178312
|
+
"python",
|
|
178313
|
+
"ruby",
|
|
178314
|
+
"java"
|
|
178315
|
+
]
|
|
178192
178316
|
},
|
|
178193
178317
|
source: { type: "string" },
|
|
178194
178318
|
message: { type: "string" },
|
|
@@ -178208,7 +178332,7 @@ var scanner_worker_schema_default = {
|
|
|
178208
178332
|
$defs: {
|
|
178209
178333
|
language: {
|
|
178210
178334
|
type: "string",
|
|
178211
|
-
enum: ["typescript", "swift", "dart", "rust"]
|
|
178335
|
+
enum: ["typescript", "swift", "dart", "rust", "go", "javascript", "python", "ruby", "java"]
|
|
178212
178336
|
},
|
|
178213
178337
|
codeSymbol: {
|
|
178214
178338
|
type: "object",
|
|
@@ -178324,6 +178448,8 @@ var scanner_worker_schema_default = {
|
|
|
178324
178448
|
};
|
|
178325
178449
|
|
|
178326
178450
|
// src/scan/code/worker/scanner-worker.ts
|
|
178451
|
+
var WORKER_OUTPUT_LIMIT_BYTES = 1024 * 1024 * 1024;
|
|
178452
|
+
var KILLED_WORKER_GRACE_MS = 500;
|
|
178327
178453
|
function createLazyWorkerResponseValidator(compile) {
|
|
178328
178454
|
let validator;
|
|
178329
178455
|
return () => {
|
|
@@ -178341,15 +178467,29 @@ function compileWorkerResponseValidator() {
|
|
|
178341
178467
|
$ref: "#/$defs/response"
|
|
178342
178468
|
});
|
|
178343
178469
|
}
|
|
178344
|
-
function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
|
|
178345
|
-
|
|
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 {
|
|
178346
178482
|
command,
|
|
178347
|
-
stdin: JSON.stringify(request)
|
|
178348
|
-
|
|
178483
|
+
stdin: JSON.stringify(request),
|
|
178484
|
+
stripEnv,
|
|
178485
|
+
timeoutMs: workerTimeoutMs(request.files.length)
|
|
178486
|
+
};
|
|
178487
|
+
}
|
|
178488
|
+
function interpretWorkerProcess(request, command, processResult) {
|
|
178349
178489
|
if (!processResult.ok) {
|
|
178350
178490
|
return {
|
|
178351
178491
|
ok: false,
|
|
178352
|
-
diagnostic:
|
|
178492
|
+
diagnostic: processResult.kind === "execution" ? scannerFailedDiagnostic(request.language, reasonOf(processResult.error)) : scannerUnavailableDiagnostic(request.language, processResult.error, command[0]),
|
|
178353
178493
|
stderr: processResult.stderr
|
|
178354
178494
|
};
|
|
178355
178495
|
}
|
|
@@ -178390,42 +178530,202 @@ function invokeScannerWorker(request, command, run = runScannerWorkerProcess) {
|
|
|
178390
178530
|
}
|
|
178391
178531
|
function clangModuleCachePath() {
|
|
178392
178532
|
const owner = typeof process.getuid === "function" ? process.getuid() : "shared";
|
|
178393
|
-
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;
|
|
178394
178551
|
}
|
|
178395
178552
|
function runScannerWorkerProcess(input) {
|
|
178553
|
+
const maxOutputBytes = input.maxOutputBytes ?? WORKER_OUTPUT_LIMIT_BYTES;
|
|
178396
178554
|
try {
|
|
178397
|
-
const moduleCachePath = clangModuleCachePath();
|
|
178398
|
-
mkdirSync(moduleCachePath, { recursive: true });
|
|
178399
178555
|
const [executable = "", ...args] = input.command;
|
|
178400
178556
|
const result = spawnSync(executable, args, {
|
|
178401
|
-
env:
|
|
178402
|
-
...process.env,
|
|
178403
|
-
CLANG_MODULE_CACHE_PATH: moduleCachePath
|
|
178404
|
-
},
|
|
178557
|
+
env: workerProcessEnv(input.stripEnv),
|
|
178405
178558
|
input: input.stdin,
|
|
178406
178559
|
encoding: "utf8",
|
|
178407
|
-
maxBuffer:
|
|
178560
|
+
maxBuffer: maxOutputBytes,
|
|
178561
|
+
killSignal: "SIGKILL",
|
|
178562
|
+
...input.timeoutMs === undefined ? {} : { timeout: input.timeoutMs }
|
|
178408
178563
|
});
|
|
178409
|
-
|
|
178410
|
-
|
|
178411
|
-
|
|
178412
|
-
|
|
178413
|
-
|
|
178414
|
-
|
|
178415
|
-
|
|
178416
|
-
|
|
178417
|
-
|
|
178418
|
-
|
|
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;
|
|
178419
178590
|
}
|
|
178420
|
-
|
|
178421
|
-
|
|
178422
|
-
|
|
178423
|
-
|
|
178424
|
-
|
|
178425
|
-
|
|
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 });
|
|
178426
178601
|
} catch (error) {
|
|
178427
|
-
|
|
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");
|
|
178694
|
+
}
|
|
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 };
|
|
178428
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" });
|
|
178429
178729
|
}
|
|
178430
178730
|
function validateWorkerResponse(value, request) {
|
|
178431
178731
|
const validator = responseValidator();
|
|
@@ -178439,129 +178739,780 @@ function validateWorkerResponse(value, request) {
|
|
|
178439
178739
|
if (response.language !== request.language) {
|
|
178440
178740
|
return "worker response language does not match the request";
|
|
178441
178741
|
}
|
|
178442
|
-
if (!responseFilesMatchRequest(response.files, request.files)) {
|
|
178443
|
-
return "worker response files must match requested files";
|
|
178742
|
+
if (!responseFilesMatchRequest(response.files, request.files)) {
|
|
178743
|
+
return "worker response files must match requested files";
|
|
178744
|
+
}
|
|
178745
|
+
return;
|
|
178746
|
+
}
|
|
178747
|
+
function formatSchemaError(errors) {
|
|
178748
|
+
const error = errors?.[0];
|
|
178749
|
+
if (error === undefined) {
|
|
178750
|
+
return "worker response does not match scanner-worker.schema.json";
|
|
178751
|
+
}
|
|
178752
|
+
const path = error.instancePath || "/";
|
|
178753
|
+
return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
|
|
178754
|
+
}
|
|
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] };
|
|
178444
179221
|
}
|
|
178445
179222
|
return;
|
|
178446
179223
|
}
|
|
178447
|
-
function
|
|
178448
|
-
|
|
178449
|
-
|
|
178450
|
-
|
|
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;
|
|
178451
179230
|
}
|
|
178452
|
-
|
|
178453
|
-
return `worker response does not match scanner-worker.schema.json at ${path}: ${error.message ?? "invalid value"}`;
|
|
179231
|
+
return resolve2(projectRoot, executable);
|
|
178454
179232
|
}
|
|
178455
|
-
function
|
|
178456
|
-
|
|
178457
|
-
|
|
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;
|
|
178458
179238
|
}
|
|
178459
|
-
|
|
178460
|
-
|
|
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()) {
|
|
178461
179254
|
return false;
|
|
178462
179255
|
}
|
|
178463
|
-
|
|
178464
|
-
|
|
179256
|
+
if (platform !== "win32") {
|
|
179257
|
+
accessSync2(path, constants2.X_OK);
|
|
179258
|
+
}
|
|
179259
|
+
return true;
|
|
179260
|
+
} catch {
|
|
179261
|
+
return false;
|
|
179262
|
+
}
|
|
178465
179263
|
}
|
|
178466
|
-
function
|
|
178467
|
-
|
|
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;
|
|
178468
179274
|
}
|
|
178469
|
-
function
|
|
178470
|
-
const
|
|
178471
|
-
|
|
178472
|
-
|
|
178473
|
-
|
|
178474
|
-
|
|
178475
|
-
|
|
178476
|
-
|
|
178477
|
-
|
|
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
|
+
});
|
|
178478
179286
|
}
|
|
178479
|
-
function
|
|
178480
|
-
const
|
|
178481
|
-
|
|
178482
|
-
|
|
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);
|
|
178483
179301
|
}
|
|
178484
|
-
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\``;
|
|
178485
179302
|
}
|
|
178486
|
-
function
|
|
178487
|
-
if (
|
|
178488
|
-
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}`);
|
|
178489
179306
|
}
|
|
178490
|
-
const
|
|
178491
|
-
|
|
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 };
|
|
178492
179318
|
}
|
|
178493
|
-
function
|
|
178494
|
-
|
|
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";
|
|
178495
179324
|
return {
|
|
178496
|
-
|
|
178497
|
-
|
|
178498
|
-
|
|
178499
|
-
|
|
178500
|
-
|
|
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
|
+
}
|
|
178501
179333
|
};
|
|
178502
179334
|
}
|
|
178503
|
-
function languageLabel(language) {
|
|
178504
|
-
return language.charAt(0).toUpperCase() + language.slice(1);
|
|
178505
|
-
}
|
|
178506
179335
|
|
|
178507
179336
|
// src/scan/code/dispatch.ts
|
|
178508
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
|
+
};
|
|
178509
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 = {
|
|
178510
179365
|
language,
|
|
178511
179366
|
scanFile(filePath, content, options, context) {
|
|
178512
|
-
const
|
|
178513
|
-
|
|
178514
|
-
|
|
178515
|
-
|
|
178516
|
-
|
|
178517
|
-
|
|
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 [];
|
|
178518
179373
|
}
|
|
178519
|
-
|
|
178520
|
-
|
|
178521
|
-
requestId: adapterOptions.requestId?.() ?? crypto.randomUUID(),
|
|
178522
|
-
language,
|
|
178523
|
-
projectRoot: context.projectRoot,
|
|
178524
|
-
files: [{ filePath, content }],
|
|
178525
|
-
options
|
|
178526
|
-
}, commandResolution.command, adapterOptions.run);
|
|
178527
|
-
if (result.ok) {
|
|
178528
|
-
const scan = result.codeFiles[0];
|
|
178529
|
-
return scan ?? emptyScan(language, filePath);
|
|
179374
|
+
if (!resolved.ok) {
|
|
179375
|
+
return failedBatch(language, files, resolved.diagnostic);
|
|
178530
179376
|
}
|
|
178531
|
-
|
|
178532
|
-
|
|
178533
|
-
|
|
178534
|
-
|
|
178535
|
-
|
|
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 })
|
|
178536
179391
|
};
|
|
179392
|
+
return { argv, runtime, adapter };
|
|
178537
179393
|
}
|
|
178538
179394
|
function scanCodeFiles(projectRoot, files, codeInclude, read, options = {}) {
|
|
178539
|
-
const
|
|
178540
|
-
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;
|
|
178541
179455
|
for (const { language, relPath } of files) {
|
|
178542
179456
|
const result = read(relPath);
|
|
178543
179457
|
if (!result.ok) {
|
|
178544
|
-
|
|
179458
|
+
slots.push({ read: false, diagnostic: result.diagnostic });
|
|
178545
179459
|
continue;
|
|
178546
179460
|
}
|
|
178547
|
-
|
|
178548
|
-
|
|
178549
|
-
|
|
178550
|
-
|
|
178551
|
-
|
|
178552
|
-
|
|
178553
|
-
});
|
|
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);
|
|
178554
179491
|
diagnostics.push(...scan.diagnostics);
|
|
178555
179492
|
codeFiles.push(scan);
|
|
178556
179493
|
}
|
|
178557
179494
|
return { codeFiles, diagnostics };
|
|
178558
179495
|
}
|
|
178559
|
-
|
|
178560
|
-
|
|
178561
|
-
|
|
178562
|
-
|
|
178563
|
-
|
|
178564
|
-
|
|
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
|
+
}
|
|
178565
179516
|
function emptyScan(language, filePath) {
|
|
178566
179517
|
return {
|
|
178567
179518
|
language,
|
|
@@ -178572,11 +179523,16 @@ function emptyScan(language, filePath) {
|
|
|
178572
179523
|
diagnostics: []
|
|
178573
179524
|
};
|
|
178574
179525
|
}
|
|
178575
|
-
function
|
|
178576
|
-
|
|
178577
|
-
}
|
|
178578
|
-
|
|
178579
|
-
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;
|
|
178580
179536
|
}
|
|
178581
179537
|
|
|
178582
179538
|
// src/scan/markdown/markdown.ts
|
|
@@ -178811,6 +179767,42 @@ function flushDanglingWith(pending, diagnostics, message) {
|
|
|
178811
179767
|
|
|
178812
179768
|
// src/query/project-scan.ts
|
|
178813
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) {
|
|
178814
179806
|
const configResult = loadConfig(options.projectRoot);
|
|
178815
179807
|
if (!configResult.ok) {
|
|
178816
179808
|
return { ok: false, diagnostics: configResult.diagnostics };
|
|
@@ -178820,32 +179812,51 @@ function scanProject(options) {
|
|
|
178820
179812
|
return { ok: false, diagnostics: [...configResult.diagnostics, ...manifestResult.diagnostics] };
|
|
178821
179813
|
}
|
|
178822
179814
|
const collectCode = options.collectCode ?? collectCodeFiles;
|
|
178823
|
-
|
|
178824
|
-
|
|
178825
|
-
|
|
178826
|
-
|
|
178827
|
-
|
|
178828
|
-
|
|
178829
|
-
|
|
178830
|
-
|
|
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 {
|
|
178831
179831
|
...contentByFile === undefined ? {} : { onContent: (relPath, content) => contentByFile.set(relPath, content) },
|
|
178832
|
-
...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 };
|
|
178833
179844
|
});
|
|
178834
|
-
|
|
179845
|
+
}
|
|
179846
|
+
function finishProjectScan(inputs, codeScan, docReads, buildGraph) {
|
|
179847
|
+
const diagnostics = [...inputs.diagnostics, ...codeScan.diagnostics];
|
|
178835
179848
|
const docFiles = [];
|
|
178836
|
-
for (const relPath of
|
|
178837
|
-
const read = readFile(relPath);
|
|
179849
|
+
for (const { relPath, read } of docReads) {
|
|
178838
179850
|
if (!read.ok) {
|
|
178839
179851
|
diagnostics.push(read.diagnostic);
|
|
178840
179852
|
continue;
|
|
178841
179853
|
}
|
|
178842
|
-
contentByFile?.set(relPath, read.content);
|
|
178843
179854
|
const scan2 = scanMarkdown(relPath, read.content);
|
|
178844
179855
|
diagnostics.push(...scan2.diagnostics);
|
|
178845
179856
|
docFiles.push(scan2);
|
|
178846
179857
|
}
|
|
178847
179858
|
const applied = applyLinkManifest({
|
|
178848
|
-
manifest:
|
|
179859
|
+
manifest: inputs.manifest,
|
|
178849
179860
|
codeFiles: codeScan.codeFiles,
|
|
178850
179861
|
docFiles,
|
|
178851
179862
|
scanDiagnostics: diagnostics
|
|
@@ -178857,25 +179868,26 @@ function scanProject(options) {
|
|
|
178857
179868
|
docFiles: applied.docFiles,
|
|
178858
179869
|
diagnostics
|
|
178859
179870
|
};
|
|
178860
|
-
if (contentByFile !== undefined) {
|
|
178861
|
-
scan.contentByFile = contentByFile;
|
|
179871
|
+
if (inputs.contentByFile !== undefined) {
|
|
179872
|
+
scan.contentByFile = inputs.contentByFile;
|
|
178862
179873
|
}
|
|
178863
|
-
if (
|
|
179874
|
+
if (buildGraph === true) {
|
|
178864
179875
|
scan.graph = buildLinkGraph(codeFiles, applied.docFiles);
|
|
178865
179876
|
}
|
|
178866
|
-
return
|
|
178867
|
-
ok: true,
|
|
178868
|
-
scan
|
|
178869
|
-
};
|
|
179877
|
+
return scan;
|
|
178870
179878
|
}
|
|
178871
179879
|
|
|
178872
179880
|
// src/lsp/project.ts
|
|
178873
179881
|
class Project {
|
|
178874
179882
|
projectRoot;
|
|
179883
|
+
options;
|
|
178875
179884
|
overlay = new Map;
|
|
179885
|
+
overlayRevision = 0;
|
|
178876
179886
|
current = emptyState();
|
|
178877
|
-
|
|
179887
|
+
cache = emptyCodeScanCache();
|
|
179888
|
+
constructor(projectRoot, options = {}) {
|
|
178878
179889
|
this.projectRoot = projectRoot;
|
|
179890
|
+
this.options = options;
|
|
178879
179891
|
}
|
|
178880
179892
|
get root() {
|
|
178881
179893
|
return this.projectRoot;
|
|
@@ -178885,48 +179897,57 @@ class Project {
|
|
|
178885
179897
|
}
|
|
178886
179898
|
setOverlay(relPath, content) {
|
|
178887
179899
|
this.overlay.set(relPath, content);
|
|
179900
|
+
this.overlayRevision += 1;
|
|
178888
179901
|
}
|
|
178889
179902
|
clearOverlay(relPath) {
|
|
178890
179903
|
this.overlay.delete(relPath);
|
|
179904
|
+
this.overlayRevision += 1;
|
|
178891
179905
|
}
|
|
178892
179906
|
resolve() {
|
|
178893
179907
|
const outcome = scanProject({
|
|
178894
|
-
|
|
178895
|
-
collectCode: (_projectRoot, include) => this.collectCode(include),
|
|
178896
|
-
collectDocs: (_projectRoot, patterns) => this.collect(patterns, false),
|
|
178897
|
-
readFile: (relPath) => this.readContent(relPath),
|
|
179908
|
+
...this.scanSources(this.overlay),
|
|
178898
179909
|
buildGraph: true,
|
|
178899
179910
|
keepContent: true
|
|
178900
179911
|
});
|
|
178901
|
-
|
|
178902
|
-
|
|
178903
|
-
|
|
178904
|
-
|
|
178905
|
-
|
|
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);
|
|
178906
179931
|
return this.current;
|
|
178907
|
-
}
|
|
178908
|
-
const {
|
|
178909
|
-
codeFiles,
|
|
178910
|
-
docFiles,
|
|
178911
|
-
diagnostics: scanDiagnostics,
|
|
178912
|
-
contentByFile,
|
|
178913
|
-
graph
|
|
178914
|
-
} = outcome.scan;
|
|
178915
|
-
const relationship = resolveLinks({
|
|
178916
|
-
codeFiles,
|
|
178917
|
-
docFiles,
|
|
178918
|
-
scanDiagnostics,
|
|
178919
|
-
audit: false
|
|
178920
179932
|
});
|
|
178921
|
-
|
|
178922
|
-
|
|
178923
|
-
|
|
178924
|
-
|
|
178925
|
-
|
|
179933
|
+
return {
|
|
179934
|
+
promise,
|
|
179935
|
+
cancel() {
|
|
179936
|
+
cancelled = true;
|
|
179937
|
+
scan.cancel();
|
|
179938
|
+
}
|
|
179939
|
+
};
|
|
179940
|
+
}
|
|
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 }
|
|
178926
179948
|
};
|
|
178927
|
-
return this.current;
|
|
178928
179949
|
}
|
|
178929
|
-
collectCode(codeInclude) {
|
|
179950
|
+
collectCode(codeInclude, overlay) {
|
|
178930
179951
|
const onDisk = collectCodeFiles(this.projectRoot, codeInclude);
|
|
178931
179952
|
const seen = new Set(onDisk.map((file) => file.relPath));
|
|
178932
179953
|
const all = [...onDisk];
|
|
@@ -178935,11 +179956,11 @@ class Project {
|
|
|
178935
179956
|
if (entry === undefined) {
|
|
178936
179957
|
continue;
|
|
178937
179958
|
}
|
|
178938
|
-
for (const relPath of
|
|
179959
|
+
for (const relPath of overlay.keys()) {
|
|
178939
179960
|
if (seen.has(relPath)) {
|
|
178940
179961
|
continue;
|
|
178941
179962
|
}
|
|
178942
|
-
if (language
|
|
179963
|
+
if (hasExcludedSuffix(language, relPath)) {
|
|
178943
179964
|
continue;
|
|
178944
179965
|
}
|
|
178945
179966
|
if (entry.patterns.some((pattern) => matchGlob(pattern, relPath))) {
|
|
@@ -178950,22 +179971,19 @@ class Project {
|
|
|
178950
179971
|
}
|
|
178951
179972
|
return all.toSorted((left, right) => comparePaths(left.relPath, right.relPath));
|
|
178952
179973
|
}
|
|
178953
|
-
readContent(relPath) {
|
|
178954
|
-
const overlaid =
|
|
179974
|
+
readContent(relPath, overlay) {
|
|
179975
|
+
const overlaid = overlay.get(relPath);
|
|
178955
179976
|
if (overlaid !== undefined) {
|
|
178956
179977
|
return { ok: true, content: overlaid };
|
|
178957
179978
|
}
|
|
178958
179979
|
return readManagedFile(this.projectRoot, relPath);
|
|
178959
179980
|
}
|
|
178960
|
-
|
|
179981
|
+
collectDocs(patterns, overlay) {
|
|
178961
179982
|
const paths = new Set(collectFiles(this.projectRoot, patterns));
|
|
178962
|
-
for (const relPath of
|
|
179983
|
+
for (const relPath of overlay.keys()) {
|
|
178963
179984
|
if (paths.has(relPath)) {
|
|
178964
179985
|
continue;
|
|
178965
179986
|
}
|
|
178966
|
-
if (isCode && relPath.endsWith(".d.ts")) {
|
|
178967
|
-
continue;
|
|
178968
|
-
}
|
|
178969
179987
|
if (patterns.some((pattern) => matchGlob(pattern, relPath))) {
|
|
178970
179988
|
paths.add(relPath);
|
|
178971
179989
|
}
|
|
@@ -178973,6 +179991,19 @@ class Project {
|
|
|
178973
179991
|
return [...paths].toSorted(comparePaths);
|
|
178974
179992
|
}
|
|
178975
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
|
+
}
|
|
178976
180007
|
function emptyState() {
|
|
178977
180008
|
const graph = buildLinkGraph([], []);
|
|
178978
180009
|
return {
|
|
@@ -178983,6 +180014,74 @@ function emptyState() {
|
|
|
178983
180014
|
};
|
|
178984
180015
|
}
|
|
178985
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
|
+
|
|
178986
180085
|
// src/lsp/transport.ts
|
|
178987
180086
|
var HEADER_SEPARATOR = `\r
|
|
178988
180087
|
\r
|
|
@@ -179050,6 +180149,10 @@ function parseContentLength(headerText) {
|
|
|
179050
180149
|
}
|
|
179051
180150
|
|
|
179052
180151
|
// src/lsp/server.ts
|
|
180152
|
+
var GLOBAL_TIMERS = {
|
|
180153
|
+
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
180154
|
+
clearTimeout: (handle) => clearTimeout(handle)
|
|
180155
|
+
};
|
|
179053
180156
|
var CAPABILITIES = {
|
|
179054
180157
|
textDocumentSync: 1,
|
|
179055
180158
|
hoverProvider: true,
|
|
@@ -179060,44 +180163,55 @@ var CAPABILITIES = {
|
|
|
179060
180163
|
class Server {
|
|
179061
180164
|
send;
|
|
179062
180165
|
project = null;
|
|
180166
|
+
scheduler = null;
|
|
179063
180167
|
openFiles = new Set;
|
|
179064
|
-
dirty = true;
|
|
179065
|
-
timer = null;
|
|
179066
180168
|
shuttingDown = false;
|
|
179067
180169
|
makeProject;
|
|
179068
180170
|
debounceMs;
|
|
179069
180171
|
onExit;
|
|
180172
|
+
timers;
|
|
180173
|
+
onScanError;
|
|
179070
180174
|
constructor(send, options = {}) {
|
|
179071
180175
|
this.send = send;
|
|
179072
180176
|
this.makeProject = options.makeProject ?? ((root) => new Project(root));
|
|
179073
180177
|
this.debounceMs = options.debounceMs ?? 50;
|
|
179074
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
|
+
`));
|
|
179075
180182
|
}
|
|
179076
180183
|
handle(message) {
|
|
179077
180184
|
switch (message.method) {
|
|
179078
180185
|
case "initialize":
|
|
179079
180186
|
this.respond(message.id, { capabilities: CAPABILITIES });
|
|
179080
|
-
this.
|
|
180187
|
+
this.start(this.makeProject(resolveRoot(message.params)));
|
|
179081
180188
|
break;
|
|
179082
180189
|
case "initialized":
|
|
179083
|
-
this.
|
|
180190
|
+
this.scheduler?.request("now");
|
|
179084
180191
|
break;
|
|
179085
180192
|
case "shutdown":
|
|
179086
180193
|
this.shuttingDown = true;
|
|
179087
|
-
this.
|
|
180194
|
+
this.scheduler?.stop();
|
|
179088
180195
|
this.respond(message.id, null);
|
|
179089
180196
|
break;
|
|
179090
180197
|
case "exit":
|
|
180198
|
+
this.scheduler?.stop();
|
|
179091
180199
|
this.onExit(this.shuttingDown ? 0 : 1);
|
|
179092
180200
|
break;
|
|
179093
180201
|
case "textDocument/didOpen":
|
|
179094
|
-
this.
|
|
180202
|
+
if (!this.shuttingDown) {
|
|
180203
|
+
this.onDidOpen(message.params);
|
|
180204
|
+
}
|
|
179095
180205
|
break;
|
|
179096
180206
|
case "textDocument/didChange":
|
|
179097
|
-
this.
|
|
180207
|
+
if (!this.shuttingDown) {
|
|
180208
|
+
this.onDidChange(message.params);
|
|
180209
|
+
}
|
|
179098
180210
|
break;
|
|
179099
180211
|
case "textDocument/didClose":
|
|
179100
|
-
this.
|
|
180212
|
+
if (!this.shuttingDown) {
|
|
180213
|
+
this.onDidClose(message.params);
|
|
180214
|
+
}
|
|
179101
180215
|
break;
|
|
179102
180216
|
case "textDocument/hover":
|
|
179103
180217
|
this.respond(message.id, this.onHover(message.params));
|
|
@@ -179114,6 +180228,16 @@ class Server {
|
|
|
179114
180228
|
}
|
|
179115
180229
|
}
|
|
179116
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
|
+
}
|
|
179117
180241
|
onDidOpen(params) {
|
|
179118
180242
|
const doc = textDocument(params);
|
|
179119
180243
|
const rel = this.relPath(doc?.uri);
|
|
@@ -179123,8 +180247,7 @@ class Server {
|
|
|
179123
180247
|
}
|
|
179124
180248
|
this.openFiles.add(rel);
|
|
179125
180249
|
this.project.setOverlay(rel, text);
|
|
179126
|
-
this.
|
|
179127
|
-
this.flush();
|
|
180250
|
+
this.scheduler?.request("now");
|
|
179128
180251
|
}
|
|
179129
180252
|
onDidChange(params) {
|
|
179130
180253
|
const record = params;
|
|
@@ -179135,7 +180258,7 @@ class Server {
|
|
|
179135
180258
|
return;
|
|
179136
180259
|
}
|
|
179137
180260
|
this.project.setOverlay(rel, text);
|
|
179138
|
-
this.
|
|
180261
|
+
this.scheduler?.request("debounced");
|
|
179139
180262
|
}
|
|
179140
180263
|
onDidClose(params) {
|
|
179141
180264
|
const doc = textDocument(params);
|
|
@@ -179145,9 +180268,8 @@ class Server {
|
|
|
179145
180268
|
}
|
|
179146
180269
|
this.openFiles.delete(rel);
|
|
179147
180270
|
this.project.clearOverlay(rel);
|
|
179148
|
-
this.dirty = true;
|
|
179149
180271
|
this.publish(rel, []);
|
|
179150
|
-
this.
|
|
180272
|
+
this.scheduler?.request("now");
|
|
179151
180273
|
}
|
|
179152
180274
|
onHover(params) {
|
|
179153
180275
|
const located = this.locate(params);
|
|
@@ -179186,7 +180308,6 @@ class Server {
|
|
|
179186
180308
|
if (rel === undefined || this.project === null || typeof position?.line !== "number" || typeof position?.character !== "number") {
|
|
179187
180309
|
return null;
|
|
179188
180310
|
}
|
|
179189
|
-
this.flush();
|
|
179190
180311
|
return {
|
|
179191
180312
|
state: this.project.state,
|
|
179192
180313
|
rel,
|
|
@@ -179194,28 +180315,14 @@ class Server {
|
|
|
179194
180315
|
position: fromLspPosition({ line: position.line, character: position.character })
|
|
179195
180316
|
};
|
|
179196
180317
|
}
|
|
179197
|
-
|
|
179198
|
-
this.dirty = true;
|
|
179199
|
-
this.clearTimer();
|
|
179200
|
-
this.timer = setTimeout(() => {
|
|
179201
|
-
this.timer = null;
|
|
179202
|
-
this.flush();
|
|
179203
|
-
}, this.debounceMs);
|
|
179204
|
-
}
|
|
179205
|
-
flush() {
|
|
179206
|
-
this.clearTimer();
|
|
179207
|
-
if (this.project === null || !this.dirty) {
|
|
179208
|
-
return;
|
|
179209
|
-
}
|
|
179210
|
-
this.dirty = false;
|
|
179211
|
-
const state = this.project.resolve();
|
|
180318
|
+
publishOpenFiles(state) {
|
|
179212
180319
|
for (const rel of this.openFiles) {
|
|
179213
180320
|
const content = state.contentByFile.get(rel) ?? "";
|
|
179214
180321
|
this.publish(rel, diagnosticsForFile(state.diagnostics, rel, content));
|
|
179215
180322
|
}
|
|
179216
180323
|
}
|
|
179217
180324
|
publish(rel, diagnostics) {
|
|
179218
|
-
if (this.project === null) {
|
|
180325
|
+
if (this.project === null || this.shuttingDown) {
|
|
179219
180326
|
return;
|
|
179220
180327
|
}
|
|
179221
180328
|
this.send({
|
|
@@ -179236,12 +180343,6 @@ class Server {
|
|
|
179236
180343
|
}
|
|
179237
180344
|
this.send({ jsonrpc: "2.0", id, result });
|
|
179238
180345
|
}
|
|
179239
|
-
clearTimer() {
|
|
179240
|
-
if (this.timer !== null) {
|
|
179241
|
-
clearTimeout(this.timer);
|
|
179242
|
-
this.timer = null;
|
|
179243
|
-
}
|
|
179244
|
-
}
|
|
179245
180346
|
}
|
|
179246
180347
|
function resolveRoot(params) {
|
|
179247
180348
|
const record = params;
|
|
@@ -179335,7 +180436,7 @@ function check(options) {
|
|
|
179335
180436
|
}
|
|
179336
180437
|
|
|
179337
180438
|
// src/query/related.ts
|
|
179338
|
-
import { isAbsolute as
|
|
180439
|
+
import { isAbsolute as isAbsolute3, relative as relative2 } from "node:path";
|
|
179339
180440
|
function normalizeChangedPaths(projectRoot, paths) {
|
|
179340
180441
|
const normalized = new Set;
|
|
179341
180442
|
for (const raw of paths) {
|
|
@@ -179343,7 +180444,7 @@ function normalizeChangedPaths(projectRoot, paths) {
|
|
|
179343
180444
|
if (trimmed === "") {
|
|
179344
180445
|
continue;
|
|
179345
180446
|
}
|
|
179346
|
-
const relativized =
|
|
180447
|
+
const relativized = isAbsolute3(trimmed) ? relative2(projectRoot, trimmed) : trimmed;
|
|
179347
180448
|
normalized.add(relativized.replace(/^(\.\/)+/, ""));
|
|
179348
180449
|
}
|
|
179349
180450
|
return [...normalized];
|
|
@@ -179765,7 +180866,7 @@ function comparePairs(left, right) {
|
|
|
179765
180866
|
// src/setup/update-cache.ts
|
|
179766
180867
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
179767
180868
|
import { homedir } from "node:os";
|
|
179768
|
-
import { dirname as dirname3, join as
|
|
180869
|
+
import { dirname as dirname3, join as join8 } from "node:path";
|
|
179769
180870
|
var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
179770
180871
|
var UPDATE_CACHE_SCHEMA_VERSION = 1;
|
|
179771
180872
|
function resolveUpdateCachePath(env = process.env, home = homedir()) {
|
|
@@ -179773,8 +180874,8 @@ function resolveUpdateCachePath(env = process.env, home = homedir()) {
|
|
|
179773
180874
|
if (explicit !== undefined && explicit.length > 0) {
|
|
179774
180875
|
return explicit;
|
|
179775
180876
|
}
|
|
179776
|
-
const cacheRoot = env.XDG_CACHE_HOME !== undefined && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME :
|
|
179777
|
-
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");
|
|
179778
180879
|
}
|
|
179779
180880
|
function readUpdateCache(path) {
|
|
179780
180881
|
let rawText;
|
|
@@ -180018,7 +181119,7 @@ function isEnabledFlag(value) {
|
|
|
180018
181119
|
|
|
180019
181120
|
// src/cli/docs.ts
|
|
180020
181121
|
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
180021
|
-
import { join as
|
|
181122
|
+
import { join as join9 } from "node:path";
|
|
180022
181123
|
|
|
180023
181124
|
// src/cli/errors.ts
|
|
180024
181125
|
class CliError extends Error {
|
|
@@ -180189,7 +181290,7 @@ function parseDocument(name, source) {
|
|
|
180189
181290
|
};
|
|
180190
181291
|
}
|
|
180191
181292
|
function createFileDocumentationReader(packageRoot) {
|
|
180192
|
-
const docsRoot =
|
|
181293
|
+
const docsRoot = join9(packageRoot, "docs", "user");
|
|
180193
181294
|
function documents() {
|
|
180194
181295
|
let fileNames;
|
|
180195
181296
|
try {
|
|
@@ -180202,7 +181303,7 @@ function createFileDocumentationReader(packageRoot) {
|
|
|
180202
181303
|
}
|
|
180203
181304
|
return fileNames.map((fileName) => {
|
|
180204
181305
|
const name = fileName.slice(0, -".md".length);
|
|
180205
|
-
return parseDocument(name, readFileSync5(
|
|
181306
|
+
return parseDocument(name, readFileSync5(join9(docsRoot, fileName), "utf8"));
|
|
180206
181307
|
});
|
|
180207
181308
|
}
|
|
180208
181309
|
return {
|
|
@@ -180599,12 +181700,12 @@ function hasHelpFlag(args) {
|
|
|
180599
181700
|
}
|
|
180600
181701
|
|
|
180601
181702
|
// src/cli/init.ts
|
|
180602
|
-
import { readFileSync as readFileSync8, statSync as
|
|
180603
|
-
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";
|
|
180604
181705
|
|
|
180605
181706
|
// src/setup/init-discovery.ts
|
|
180606
|
-
import { existsSync as
|
|
180607
|
-
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";
|
|
180608
181709
|
var HIGH_CONFIDENCE_DIR_NAMES = new Set([
|
|
180609
181710
|
"docs/specs",
|
|
180610
181711
|
"specs",
|
|
@@ -180634,20 +181735,39 @@ var EXCLUDED_DIR_SEGMENTS = new Set([
|
|
|
180634
181735
|
"contributing"
|
|
180635
181736
|
]);
|
|
180636
181737
|
var IGNORED_WALK_SEGMENTS = new Set(["node_modules", ".git", "dist", "build"]);
|
|
180637
|
-
var
|
|
180638
|
-
|
|
180639
|
-
|
|
180640
|
-
"packages/*/src/**/*.ts",
|
|
180641
|
-
"apps/*/src/**/*.ts"
|
|
180642
|
-
];
|
|
181738
|
+
var SCRIPT_SOURCE_ROOTS = ["src", "lib", "packages/*/src", "apps/*/src"];
|
|
181739
|
+
var TYPESCRIPT_PATTERNS = scriptPatterns("typescript");
|
|
181740
|
+
var JAVASCRIPT_PATTERNS = scriptPatterns("javascript");
|
|
180643
181741
|
var SWIFT_PATTERNS = ["Sources/**/*.swift", "*/Sources/**/*.swift"];
|
|
180644
181742
|
var DART_PATTERNS = ["lib/**/*.dart"];
|
|
180645
181743
|
var RUST_PATTERNS = ["src/**/*.rs", "*/src/**/*.rs"];
|
|
181744
|
+
var GO_PATTERNS = ["*.go", "cmd/**/*.go", "internal/**/*.go", "pkg/**/*.go"];
|
|
181745
|
+
var RUBY_PATTERNS = ["lib/**/*.rb", "app/**/*.rb"];
|
|
180646
181746
|
var LANGUAGE_PATTERNS = {
|
|
180647
181747
|
typescript: TYPESCRIPT_PATTERNS,
|
|
180648
181748
|
swift: SWIFT_PATTERNS,
|
|
180649
181749
|
dart: DART_PATTERNS,
|
|
180650
|
-
rust: RUST_PATTERNS
|
|
181750
|
+
rust: RUST_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
|
|
180651
181771
|
};
|
|
180652
181772
|
function discoverRepository(projectRoot) {
|
|
180653
181773
|
return {
|
|
@@ -180731,8 +181851,8 @@ function discoverCodeScope(projectRoot) {
|
|
|
180731
181851
|
};
|
|
180732
181852
|
}
|
|
180733
181853
|
function discoverAgentTarget(projectRoot) {
|
|
180734
|
-
const hasAgentsDir =
|
|
180735
|
-
const hasClaudeDir =
|
|
181854
|
+
const hasAgentsDir = existsSync4(join10(projectRoot, ".agents"));
|
|
181855
|
+
const hasClaudeDir = existsSync4(join10(projectRoot, ".claude"));
|
|
180736
181856
|
let defaultTarget = "none";
|
|
180737
181857
|
let recommendedTarget = "none";
|
|
180738
181858
|
let message;
|
|
@@ -180759,7 +181879,7 @@ function discoverAgentTarget(projectRoot) {
|
|
|
180759
181879
|
};
|
|
180760
181880
|
}
|
|
180761
181881
|
function collectMarkdownFiles(projectRoot, currentDir = ".") {
|
|
180762
|
-
const absoluteDir =
|
|
181882
|
+
const absoluteDir = join10(projectRoot, currentDir);
|
|
180763
181883
|
let entries;
|
|
180764
181884
|
try {
|
|
180765
181885
|
entries = readdirSync3(absoluteDir);
|
|
@@ -180773,10 +181893,10 @@ function collectMarkdownFiles(projectRoot, currentDir = ".") {
|
|
|
180773
181893
|
if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
|
|
180774
181894
|
continue;
|
|
180775
181895
|
}
|
|
180776
|
-
const absolutePath =
|
|
181896
|
+
const absolutePath = join10(projectRoot, relPath);
|
|
180777
181897
|
let stats;
|
|
180778
181898
|
try {
|
|
180779
|
-
stats =
|
|
181899
|
+
stats = statSync3(absolutePath);
|
|
180780
181900
|
} catch {
|
|
180781
181901
|
continue;
|
|
180782
181902
|
}
|
|
@@ -180814,8 +181934,22 @@ function scoreDocsDirectory(directory) {
|
|
|
180814
181934
|
}
|
|
180815
181935
|
return score;
|
|
180816
181936
|
}
|
|
181937
|
+
function scriptPatterns(language) {
|
|
181938
|
+
return SCRIPT_SOURCE_ROOTS.flatMap((root) => LANGUAGE_SUFFIXES[language].map((suffix) => `${root}/**/*${suffix}`));
|
|
181939
|
+
}
|
|
180817
181940
|
function activeCodePatterns(projectRoot, language) {
|
|
180818
|
-
|
|
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`)];
|
|
180819
181953
|
}
|
|
180820
181954
|
function countCodeFiles(projectRoot, patterns, language) {
|
|
180821
181955
|
const seen = new Set;
|
|
@@ -180831,28 +181965,8 @@ function countCodeFiles(projectRoot, patterns, language) {
|
|
|
180831
181965
|
function isExcludedCodeFile(filePath, language) {
|
|
180832
181966
|
const lower = filePath.toLowerCase();
|
|
180833
181967
|
const segments = filePath.split("/");
|
|
180834
|
-
if (language ===
|
|
180835
|
-
|
|
180836
|
-
return true;
|
|
180837
|
-
}
|
|
180838
|
-
if (lower.endsWith(".test.ts") || lower.endsWith(".spec.ts") || segments.includes("__tests__") || segments.includes("tests") || segments.includes("test")) {
|
|
180839
|
-
return true;
|
|
180840
|
-
}
|
|
180841
|
-
}
|
|
180842
|
-
if (language === "swift") {
|
|
180843
|
-
if (lower.endsWith("tests.swift") || segments.includes("Tests") || segments.includes("tests")) {
|
|
180844
|
-
return true;
|
|
180845
|
-
}
|
|
180846
|
-
}
|
|
180847
|
-
if (language === "dart") {
|
|
180848
|
-
if (lower.endsWith("_test.dart") || segments.includes("test")) {
|
|
180849
|
-
return true;
|
|
180850
|
-
}
|
|
180851
|
-
}
|
|
180852
|
-
if (language === "rust") {
|
|
180853
|
-
if (segments.includes("target") || segments.includes("tests") || segments.includes("benches") || segments.includes("examples")) {
|
|
180854
|
-
return true;
|
|
180855
|
-
}
|
|
181968
|
+
if (hasExcludedSuffix(language, lower) || TEST_FILE_PATTERNS[language]?.test(lower) === true || EXCLUDED_CODE_SEGMENTS[language].some((segment) => segments.includes(segment))) {
|
|
181969
|
+
return true;
|
|
180856
181970
|
}
|
|
180857
181971
|
if (segments.some((segment) => ["generated", "gen", ".dart_tool", "build"].includes(segment.toLowerCase()))) {
|
|
180858
181972
|
return true;
|
|
@@ -180861,17 +181975,17 @@ function isExcludedCodeFile(filePath, language) {
|
|
|
180861
181975
|
}
|
|
180862
181976
|
|
|
180863
181977
|
// src/setup/init-plan.ts
|
|
180864
|
-
import { existsSync as
|
|
180865
|
-
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";
|
|
180866
181980
|
|
|
180867
181981
|
// src/setup/skill-assets.ts
|
|
180868
181982
|
import { cpSync, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
180869
|
-
import { join as
|
|
181983
|
+
import { join as join11 } from "node:path";
|
|
180870
181984
|
function classifyManagedPath(projectRoot, relativePath) {
|
|
180871
181985
|
const segments = relativePath.split(/[/\\]/).filter((segment) => segment.length > 0);
|
|
180872
181986
|
let current = projectRoot;
|
|
180873
181987
|
for (const [index, segment] of segments.entries()) {
|
|
180874
|
-
current =
|
|
181988
|
+
current = join11(current, segment);
|
|
180875
181989
|
const isLast = index === segments.length - 1;
|
|
180876
181990
|
let stats;
|
|
180877
181991
|
try {
|
|
@@ -180918,7 +182032,7 @@ function compareSkillTree(installedDir, templateDir) {
|
|
|
180918
182032
|
continue;
|
|
180919
182033
|
}
|
|
180920
182034
|
installedFiles.delete(file);
|
|
180921
|
-
if (readFileOrUndefined(
|
|
182035
|
+
if (readFileOrUndefined(join11(installedDir, file)) !== readFileOrUndefined(join11(templateDir, file))) {
|
|
180922
182036
|
modified.push(file);
|
|
180923
182037
|
}
|
|
180924
182038
|
}
|
|
@@ -180929,7 +182043,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180929
182043
|
return;
|
|
180930
182044
|
}
|
|
180931
182045
|
const kind = classifyManagedPath(projectRoot, operation.path);
|
|
180932
|
-
const destinationDir =
|
|
182046
|
+
const destinationDir = join11(projectRoot, operation.path);
|
|
180933
182047
|
if (operation.action === "remove") {
|
|
180934
182048
|
if (kind === "directory") {
|
|
180935
182049
|
rmSync(destinationDir, { recursive: true, force: true });
|
|
@@ -180947,7 +182061,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180947
182061
|
rmSync(destinationDir, { recursive: true, force: true });
|
|
180948
182062
|
}
|
|
180949
182063
|
mkdirSync3(destinationDir, { recursive: true });
|
|
180950
|
-
cpSync(
|
|
182064
|
+
cpSync(join11(packageRoot, "templates", "skills", skillName), destinationDir, {
|
|
180951
182065
|
recursive: true,
|
|
180952
182066
|
force: true
|
|
180953
182067
|
});
|
|
@@ -180955,7 +182069,7 @@ function applySkillOperation(projectRoot, packageRoot, operation) {
|
|
|
180955
182069
|
function collectSkillFiles(root, prefix, files) {
|
|
180956
182070
|
let entries;
|
|
180957
182071
|
try {
|
|
180958
|
-
entries = readdirSync4(
|
|
182072
|
+
entries = readdirSync4(join11(root, prefix), { withFileTypes: true });
|
|
180959
182073
|
} catch {
|
|
180960
182074
|
return;
|
|
180961
182075
|
}
|
|
@@ -181066,14 +182180,14 @@ function buildConfigFromScope(scope) {
|
|
|
181066
182180
|
};
|
|
181067
182181
|
}
|
|
181068
182182
|
function listDistributableSkills(packageRoot) {
|
|
181069
|
-
const skillsRoot =
|
|
182183
|
+
const skillsRoot = join12(packageRoot, "templates", "skills");
|
|
181070
182184
|
let entries;
|
|
181071
182185
|
try {
|
|
181072
182186
|
entries = readdirSync5(skillsRoot);
|
|
181073
182187
|
} catch {
|
|
181074
182188
|
return [];
|
|
181075
182189
|
}
|
|
181076
|
-
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();
|
|
181077
182191
|
}
|
|
181078
182192
|
function formatInitPlan(plan) {
|
|
181079
182193
|
const lines = [];
|
|
@@ -181178,7 +182292,7 @@ function planSkillOperations(input) {
|
|
|
181178
182292
|
if (!availableSkills.has(skillName)) {
|
|
181179
182293
|
continue;
|
|
181180
182294
|
}
|
|
181181
|
-
const destinationDir =
|
|
182295
|
+
const destinationDir = join12(input.projectRoot, destination, skillName);
|
|
181182
182296
|
const path = relative3(input.projectRoot, destinationDir);
|
|
181183
182297
|
const kind = classifyManagedPath(input.projectRoot, path);
|
|
181184
182298
|
if (kind !== "absent" && kind !== "directory") {
|
|
@@ -181217,7 +182331,7 @@ function planLegacySkillOperations(input) {
|
|
|
181217
182331
|
const operations = [];
|
|
181218
182332
|
const leftover = [];
|
|
181219
182333
|
for (const skillName of LEGACY_SKILL_NAMES) {
|
|
181220
|
-
const destinationDir =
|
|
182334
|
+
const destinationDir = join12(input.projectRoot, input.destination, skillName);
|
|
181221
182335
|
const path = relative3(input.projectRoot, destinationDir);
|
|
181222
182336
|
const kind = classifyManagedPath(input.projectRoot, path);
|
|
181223
182337
|
if (kind === "absent") {
|
|
@@ -181297,7 +182411,7 @@ function resolveInitWithAgentTarget(discovery, options) {
|
|
|
181297
182411
|
return { target: discovery.agent.defaultTarget, error: undefined };
|
|
181298
182412
|
}
|
|
181299
182413
|
function readExistingConfig(projectRoot) {
|
|
181300
|
-
const configPath =
|
|
182414
|
+
const configPath = join12(projectRoot, CONFIG_FILE_NAME2);
|
|
181301
182415
|
let rawText;
|
|
181302
182416
|
try {
|
|
181303
182417
|
rawText = readFileSync7(configPath, "utf8");
|
|
@@ -181548,7 +182662,7 @@ function executePlan(projectRoot, plan, packageRoot) {
|
|
|
181548
182662
|
}
|
|
181549
182663
|
}
|
|
181550
182664
|
function executeFileOperation(projectRoot, operation) {
|
|
181551
|
-
const absolutePath =
|
|
182665
|
+
const absolutePath = join13(projectRoot, operation.path);
|
|
181552
182666
|
if (operation.action === "create" && operation.content !== undefined) {
|
|
181553
182667
|
writeFileSync2(absolutePath, operation.content, "utf8");
|
|
181554
182668
|
}
|
|
@@ -181557,10 +182671,10 @@ function executeFileOperation(projectRoot, operation) {
|
|
|
181557
182671
|
}
|
|
181558
182672
|
}
|
|
181559
182673
|
function resolveProjectRoot(root, command) {
|
|
181560
|
-
const projectRoot =
|
|
182674
|
+
const projectRoot = resolve3(root);
|
|
181561
182675
|
let stats;
|
|
181562
182676
|
try {
|
|
181563
|
-
stats =
|
|
182677
|
+
stats = statSync4(projectRoot);
|
|
181564
182678
|
} catch {
|
|
181565
182679
|
throw new InitCliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
|
|
181566
182680
|
}
|
|
@@ -181595,21 +182709,12 @@ ${block.content}`;
|
|
|
181595
182709
|
const fence = codeFence(block.content);
|
|
181596
182710
|
return `${header}
|
|
181597
182711
|
|
|
181598
|
-
${fence}${fenceLanguage(block
|
|
182712
|
+
${fence}${fenceLanguage(block)}
|
|
181599
182713
|
${block.content}
|
|
181600
182714
|
${fence}`;
|
|
181601
182715
|
}
|
|
181602
|
-
function fenceLanguage(
|
|
181603
|
-
|
|
181604
|
-
return "swift";
|
|
181605
|
-
}
|
|
181606
|
-
if (language === "dart") {
|
|
181607
|
-
return "dart";
|
|
181608
|
-
}
|
|
181609
|
-
if (language === "rust") {
|
|
181610
|
-
return "rust";
|
|
181611
|
-
}
|
|
181612
|
-
return "ts";
|
|
182716
|
+
function fenceLanguage(block) {
|
|
182717
|
+
return block.language === undefined ? "ts" : codeFenceLanguage(block.language, block.filePath);
|
|
181613
182718
|
}
|
|
181614
182719
|
function codeFence(content) {
|
|
181615
182720
|
let longestRun = 0;
|
|
@@ -181741,12 +182846,12 @@ function formatRelatedSummary(summary) {
|
|
|
181741
182846
|
}
|
|
181742
182847
|
|
|
181743
182848
|
// src/cli/upgrade.ts
|
|
181744
|
-
import { statSync as
|
|
181745
|
-
import { resolve as
|
|
182849
|
+
import { statSync as statSync5 } from "node:fs";
|
|
182850
|
+
import { resolve as resolve4 } from "node:path";
|
|
181746
182851
|
|
|
181747
182852
|
// src/setup/upgrade-plan.ts
|
|
181748
|
-
import { existsSync as
|
|
181749
|
-
import { join as
|
|
182853
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
182854
|
+
import { join as join14, relative as relative4 } from "node:path";
|
|
181750
182855
|
var MANAGED_SKILL_NAME = INIT_SKILL_NAMES[0];
|
|
181751
182856
|
function reportCliVersion(currentVersion, latest) {
|
|
181752
182857
|
if (latest.status !== "ok") {
|
|
@@ -181764,7 +182869,7 @@ function resolveUpgradeAgentTarget(discovery, explicitTarget) {
|
|
|
181764
182869
|
return explicitTarget ?? discovery.agent.defaultTarget;
|
|
181765
182870
|
}
|
|
181766
182871
|
function inspectManagedSkill(input) {
|
|
181767
|
-
const destinationDir =
|
|
182872
|
+
const destinationDir = join14(input.projectRoot, input.destination, MANAGED_SKILL_NAME);
|
|
181768
182873
|
const path = toRootRelative(input.projectRoot, destinationDir);
|
|
181769
182874
|
const empty = {
|
|
181770
182875
|
destination: input.destination,
|
|
@@ -181780,7 +182885,7 @@ function inspectManagedSkill(input) {
|
|
|
181780
182885
|
if (!input.templateAvailable) {
|
|
181781
182886
|
return { ...empty, state: "template-missing" };
|
|
181782
182887
|
}
|
|
181783
|
-
const comparison = compareSkillTree(destinationDir,
|
|
182888
|
+
const comparison = compareSkillTree(destinationDir, join14(input.packageRoot, "templates", "skills", MANAGED_SKILL_NAME));
|
|
181784
182889
|
const drifted = comparison.modified.length > 0 || comparison.missing.length > 0 || comparison.extra.length > 0;
|
|
181785
182890
|
return {
|
|
181786
182891
|
destination: input.destination,
|
|
@@ -181794,13 +182899,13 @@ function inspectManagedSkill(input) {
|
|
|
181794
182899
|
function inspectLegacySkills(projectRoot, destination) {
|
|
181795
182900
|
const reports = [];
|
|
181796
182901
|
for (const skillName of LEGACY_SKILL_NAMES) {
|
|
181797
|
-
const destinationDir =
|
|
182902
|
+
const destinationDir = join14(projectRoot, destination, skillName);
|
|
181798
182903
|
const path = toRootRelative(projectRoot, destinationDir);
|
|
181799
182904
|
const kind = classifyManagedPath(projectRoot, path);
|
|
181800
182905
|
if (kind === "absent") {
|
|
181801
182906
|
continue;
|
|
181802
182907
|
}
|
|
181803
|
-
if (kind === "symlinked-parent" && !
|
|
182908
|
+
if (kind === "symlinked-parent" && !existsSync6(destinationDir)) {
|
|
181804
182909
|
continue;
|
|
181805
182910
|
}
|
|
181806
182911
|
reports.push({ path, kind });
|
|
@@ -182064,10 +183169,10 @@ function parseAgentTarget(value) {
|
|
|
182064
183169
|
return value;
|
|
182065
183170
|
}
|
|
182066
183171
|
function resolveProjectRoot2(root) {
|
|
182067
|
-
const projectRoot =
|
|
183172
|
+
const projectRoot = resolve4(root);
|
|
182068
183173
|
let stats;
|
|
182069
183174
|
try {
|
|
182070
|
-
stats =
|
|
183175
|
+
stats = statSync5(projectRoot);
|
|
182071
183176
|
} catch {
|
|
182072
183177
|
throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance("upgrade"));
|
|
182073
183178
|
}
|
|
@@ -182092,10 +183197,10 @@ function parseGraphOptions(args) {
|
|
|
182092
183197
|
return parseCommandOptions("graph", args);
|
|
182093
183198
|
}
|
|
182094
183199
|
function resolveProjectRoot3(root, command) {
|
|
182095
|
-
const projectRoot =
|
|
183200
|
+
const projectRoot = resolve5(root);
|
|
182096
183201
|
let stats;
|
|
182097
183202
|
try {
|
|
182098
|
-
stats =
|
|
183203
|
+
stats = statSync6(projectRoot);
|
|
182099
183204
|
} catch {
|
|
182100
183205
|
throw new CliError(`Root path does not exist: ${root}`, rootPathGuidance(command));
|
|
182101
183206
|
}
|
|
@@ -182219,7 +183324,7 @@ function repairGuidanceFor(diagnostics, projectRoot) {
|
|
|
182219
183324
|
if (invalid.length === 0) {
|
|
182220
183325
|
return;
|
|
182221
183326
|
}
|
|
182222
|
-
return
|
|
183327
|
+
return existsSync7(join15(projectRoot, CONFIG_FILE_NAME)) ? configRepairGuidance() : configSetupGuidance();
|
|
182223
183328
|
}
|
|
182224
183329
|
function unknownCommandGuidance(command) {
|
|
182225
183330
|
const suggestion = nearestMatch(command, SUBCOMMANDS);
|
|
@@ -182294,7 +183399,7 @@ function run(argv, io = {
|
|
|
182294
183399
|
function rootOptionOf(argv, currentDirectory) {
|
|
182295
183400
|
const flagIndex = argv.indexOf("--root");
|
|
182296
183401
|
const value = flagIndex === -1 ? undefined : argv[flagIndex + 1];
|
|
182297
|
-
return value === undefined ? currentDirectory :
|
|
183402
|
+
return value === undefined ? currentDirectory : resolve5(currentDirectory, value);
|
|
182298
183403
|
}
|
|
182299
183404
|
function writeUpdateNotice(argv, io, initRuntime, cliRuntime) {
|
|
182300
183405
|
const latest = cliRuntime.latest;
|