@untangle-code/parser-registry 0.0.0-stage → 0.1.1
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/LICENSE +661 -0
- package/NOTICE +90 -0
- package/README.md +352 -2
- package/THIRD_PARTY_LICENSES.md +91 -0
- package/dist/boundaries/expression.d.ts +10 -0
- package/dist/boundaries/expression.d.ts.map +1 -0
- package/dist/boundaries/expression.js +95 -0
- package/dist/boundaries/expression.js.map +1 -0
- package/dist/boundaries/extract.d.ts +12 -0
- package/dist/boundaries/extract.d.ts.map +1 -0
- package/dist/boundaries/extract.js +259 -0
- package/dist/boundaries/extract.js.map +1 -0
- package/dist/boundaries/python.d.ts +6 -0
- package/dist/boundaries/python.d.ts.map +1 -0
- package/dist/boundaries/python.js +154 -0
- package/dist/boundaries/python.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/astgrep-backend/adapter.d.ts +101 -0
- package/dist/languages/astgrep-backend/adapter.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/adapter.js +130 -0
- package/dist/languages/astgrep-backend/adapter.js.map +1 -0
- package/dist/languages/astgrep-backend/index.d.ts +29 -0
- package/dist/languages/astgrep-backend/index.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/index.js +28 -0
- package/dist/languages/astgrep-backend/index.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/bash.d.ts +38 -0
- package/dist/languages/astgrep-backend/lang/bash.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/bash.js +42 -0
- package/dist/languages/astgrep-backend/lang/bash.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/c.d.ts +33 -0
- package/dist/languages/astgrep-backend/lang/c.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/c.js +85 -0
- package/dist/languages/astgrep-backend/lang/c.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/cpp.d.ts +27 -0
- package/dist/languages/astgrep-backend/lang/cpp.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/cpp.js +38 -0
- package/dist/languages/astgrep-backend/lang/cpp.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/csharp.d.ts +24 -0
- package/dist/languages/astgrep-backend/lang/csharp.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/csharp.js +102 -0
- package/dist/languages/astgrep-backend/lang/csharp.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/index.d.ts +29 -0
- package/dist/languages/astgrep-backend/lang/index.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/index.js +22 -0
- package/dist/languages/astgrep-backend/lang/index.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/java.d.ts +26 -0
- package/dist/languages/astgrep-backend/lang/java.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/java.js +89 -0
- package/dist/languages/astgrep-backend/lang/java.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/kotlin.d.ts +44 -0
- package/dist/languages/astgrep-backend/lang/kotlin.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/kotlin.js +94 -0
- package/dist/languages/astgrep-backend/lang/kotlin.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/php.d.ts +23 -0
- package/dist/languages/astgrep-backend/lang/php.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/php.js +80 -0
- package/dist/languages/astgrep-backend/lang/php.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/ruby.d.ts +37 -0
- package/dist/languages/astgrep-backend/lang/ruby.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/ruby.js +70 -0
- package/dist/languages/astgrep-backend/lang/ruby.js.map +1 -0
- package/dist/languages/astgrep-backend/lang/swift.d.ts +34 -0
- package/dist/languages/astgrep-backend/lang/swift.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/lang/swift.js +86 -0
- package/dist/languages/astgrep-backend/lang/swift.js.map +1 -0
- package/dist/languages/astgrep-backend/languages.d.ts +55 -0
- package/dist/languages/astgrep-backend/languages.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/languages.js +214 -0
- package/dist/languages/astgrep-backend/languages.js.map +1 -0
- package/dist/languages/astgrep-backend/napi.d.ts +71 -0
- package/dist/languages/astgrep-backend/napi.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/napi.js +176 -0
- package/dist/languages/astgrep-backend/napi.js.map +1 -0
- package/dist/languages/astgrep-backend/syntax.d.ts +88 -0
- package/dist/languages/astgrep-backend/syntax.d.ts.map +1 -0
- package/dist/languages/astgrep-backend/syntax.js +180 -0
- package/dist/languages/astgrep-backend/syntax.js.map +1 -0
- package/dist/languages/index.d.ts +14 -0
- package/dist/languages/index.d.ts.map +1 -0
- package/dist/languages/index.js +37 -0
- package/dist/languages/index.js.map +1 -0
- package/dist/languages/javascript/index.d.ts +4 -0
- package/dist/languages/javascript/index.d.ts.map +1 -0
- package/dist/languages/javascript/index.js +7 -0
- package/dist/languages/javascript/index.js.map +1 -0
- package/dist/languages/jsx/index.d.ts +4 -0
- package/dist/languages/jsx/index.d.ts.map +1 -0
- package/dist/languages/jsx/index.js +7 -0
- package/dist/languages/jsx/index.js.map +1 -0
- package/dist/languages/prose/index.d.ts +53 -0
- package/dist/languages/prose/index.d.ts.map +1 -0
- package/dist/languages/prose/index.js +172 -0
- package/dist/languages/prose/index.js.map +1 -0
- package/dist/languages/prose/line-bytes.d.ts +31 -0
- package/dist/languages/prose/line-bytes.d.ts.map +1 -0
- package/dist/languages/prose/line-bytes.js +39 -0
- package/dist/languages/prose/line-bytes.js.map +1 -0
- package/dist/languages/python/create-adapter.d.ts +20 -0
- package/dist/languages/python/create-adapter.d.ts.map +1 -0
- package/dist/languages/python/create-adapter.js +9 -0
- package/dist/languages/python/create-adapter.js.map +1 -0
- package/dist/languages/python/index.d.ts +4 -0
- package/dist/languages/python/index.d.ts.map +1 -0
- package/dist/languages/python/index.js +6 -0
- package/dist/languages/python/index.js.map +1 -0
- package/dist/languages/python/profile.d.ts +36 -0
- package/dist/languages/python/profile.d.ts.map +1 -0
- package/dist/languages/python/profile.js +282 -0
- package/dist/languages/python/profile.js.map +1 -0
- package/dist/languages/python/signature.d.ts +53 -0
- package/dist/languages/python/signature.d.ts.map +1 -0
- package/dist/languages/python/signature.js +188 -0
- package/dist/languages/python/signature.js.map +1 -0
- package/dist/languages/python/table.d.ts +46 -0
- package/dist/languages/python/table.d.ts.map +1 -0
- package/dist/languages/python/table.js +88 -0
- package/dist/languages/python/table.js.map +1 -0
- package/dist/languages/shared/call-detection.d.ts +17 -0
- package/dist/languages/shared/call-detection.d.ts.map +1 -0
- package/dist/languages/shared/call-detection.js +86 -0
- package/dist/languages/shared/call-detection.js.map +1 -0
- package/dist/languages/shared/chunks.d.ts +28 -0
- package/dist/languages/shared/chunks.d.ts.map +1 -0
- package/dist/languages/shared/chunks.js +181 -0
- package/dist/languages/shared/chunks.js.map +1 -0
- package/dist/languages/shared/create-adapter.d.ts +11 -0
- package/dist/languages/shared/create-adapter.d.ts.map +1 -0
- package/dist/languages/shared/create-adapter.js +90 -0
- package/dist/languages/shared/create-adapter.js.map +1 -0
- package/dist/languages/shared/decisions.d.ts +45 -0
- package/dist/languages/shared/decisions.d.ts.map +1 -0
- package/dist/languages/shared/decisions.js +141 -0
- package/dist/languages/shared/decisions.js.map +1 -0
- package/dist/languages/shared/dialect.d.ts +18 -0
- package/dist/languages/shared/dialect.d.ts.map +1 -0
- package/dist/languages/shared/dialect.js +37 -0
- package/dist/languages/shared/dialect.js.map +1 -0
- package/dist/languages/shared/doc-binding.d.ts +26 -0
- package/dist/languages/shared/doc-binding.d.ts.map +1 -0
- package/dist/languages/shared/doc-binding.js +50 -0
- package/dist/languages/shared/doc-binding.js.map +1 -0
- package/dist/languages/shared/engine.d.ts +49 -0
- package/dist/languages/shared/engine.d.ts.map +1 -0
- package/dist/languages/shared/engine.js +317 -0
- package/dist/languages/shared/engine.js.map +1 -0
- package/dist/languages/shared/extract-symbols.d.ts +5 -0
- package/dist/languages/shared/extract-symbols.d.ts.map +1 -0
- package/dist/languages/shared/extract-symbols.js +31 -0
- package/dist/languages/shared/extract-symbols.js.map +1 -0
- package/dist/languages/shared/fingerprint.d.ts +7 -0
- package/dist/languages/shared/fingerprint.d.ts.map +1 -0
- package/dist/languages/shared/fingerprint.js +95 -0
- package/dist/languages/shared/fingerprint.js.map +1 -0
- package/dist/languages/shared/grammar-loader.d.ts +15 -0
- package/dist/languages/shared/grammar-loader.d.ts.map +1 -0
- package/dist/languages/shared/grammar-loader.js +48 -0
- package/dist/languages/shared/grammar-loader.js.map +1 -0
- package/dist/languages/shared/import-detection.d.ts +24 -0
- package/dist/languages/shared/import-detection.d.ts.map +1 -0
- package/dist/languages/shared/import-detection.js +138 -0
- package/dist/languages/shared/import-detection.js.map +1 -0
- package/dist/languages/shared/js-family-profile.d.ts +5 -0
- package/dist/languages/shared/js-family-profile.d.ts.map +1 -0
- package/dist/languages/shared/js-family-profile.js +179 -0
- package/dist/languages/shared/js-family-profile.js.map +1 -0
- package/dist/languages/shared/node-kinds.d.ts +78 -0
- package/dist/languages/shared/node-kinds.d.ts.map +1 -0
- package/dist/languages/shared/node-kinds.js +141 -0
- package/dist/languages/shared/node-kinds.js.map +1 -0
- package/dist/languages/shared/node-utils.d.ts +31 -0
- package/dist/languages/shared/node-utils.d.ts.map +1 -0
- package/dist/languages/shared/node-utils.js +89 -0
- package/dist/languages/shared/node-utils.js.map +1 -0
- package/dist/languages/shared/parse-diagnostics.d.ts +44 -0
- package/dist/languages/shared/parse-diagnostics.d.ts.map +1 -0
- package/dist/languages/shared/parse-diagnostics.js +64 -0
- package/dist/languages/shared/parse-diagnostics.js.map +1 -0
- package/dist/languages/shared/parse-limits.d.ts +24 -0
- package/dist/languages/shared/parse-limits.d.ts.map +1 -0
- package/dist/languages/shared/parse-limits.js +134 -0
- package/dist/languages/shared/parse-limits.js.map +1 -0
- package/dist/languages/shared/profile.d.ts +149 -0
- package/dist/languages/shared/profile.d.ts.map +1 -0
- package/dist/languages/shared/profile.js +9 -0
- package/dist/languages/shared/profile.js.map +1 -0
- package/dist/languages/shared/scope-model.d.ts +32 -0
- package/dist/languages/shared/scope-model.d.ts.map +1 -0
- package/dist/languages/shared/scope-model.js +2 -0
- package/dist/languages/shared/scope-model.js.map +1 -0
- package/dist/languages/shared/scope-naming.d.ts +20 -0
- package/dist/languages/shared/scope-naming.d.ts.map +1 -0
- package/dist/languages/shared/scope-naming.js +118 -0
- package/dist/languages/shared/scope-naming.js.map +1 -0
- package/dist/languages/shared/scope-signature.d.ts +14 -0
- package/dist/languages/shared/scope-signature.d.ts.map +1 -0
- package/dist/languages/shared/scope-signature.js +82 -0
- package/dist/languages/shared/scope-signature.js.map +1 -0
- package/dist/languages/shared/signature.d.ts +55 -0
- package/dist/languages/shared/signature.d.ts.map +1 -0
- package/dist/languages/shared/signature.js +147 -0
- package/dist/languages/shared/signature.js.map +1 -0
- package/dist/languages/shared/syntax.d.ts +115 -0
- package/dist/languages/shared/syntax.d.ts.map +1 -0
- package/dist/languages/shared/syntax.js +2 -0
- package/dist/languages/shared/syntax.js.map +1 -0
- package/dist/languages/shared/table-profile-decisions.d.ts +54 -0
- package/dist/languages/shared/table-profile-decisions.d.ts.map +1 -0
- package/dist/languages/shared/table-profile-decisions.js +245 -0
- package/dist/languages/shared/table-profile-decisions.js.map +1 -0
- package/dist/languages/shared/table-profile-module-facts.d.ts +29 -0
- package/dist/languages/shared/table-profile-module-facts.d.ts.map +1 -0
- package/dist/languages/shared/table-profile-module-facts.js +139 -0
- package/dist/languages/shared/table-profile-module-facts.js.map +1 -0
- package/dist/languages/shared/table-profile-queries.d.ts +27 -0
- package/dist/languages/shared/table-profile-queries.d.ts.map +1 -0
- package/dist/languages/shared/table-profile-queries.js +70 -0
- package/dist/languages/shared/table-profile-queries.js.map +1 -0
- package/dist/languages/shared/table-profile-scopes.d.ts +46 -0
- package/dist/languages/shared/table-profile-scopes.d.ts.map +1 -0
- package/dist/languages/shared/table-profile-scopes.js +180 -0
- package/dist/languages/shared/table-profile-scopes.js.map +1 -0
- package/dist/languages/shared/table-profile.d.ts +302 -0
- package/dist/languages/shared/table-profile.d.ts.map +1 -0
- package/dist/languages/shared/table-profile.js +125 -0
- package/dist/languages/shared/table-profile.js.map +1 -0
- package/dist/languages/shared/tree-sitter-types.d.ts +44 -0
- package/dist/languages/shared/tree-sitter-types.d.ts.map +1 -0
- package/dist/languages/shared/tree-sitter-types.js +2 -0
- package/dist/languages/shared/tree-sitter-types.js.map +1 -0
- package/dist/languages/structured-text/index.d.ts +48 -0
- package/dist/languages/structured-text/index.d.ts.map +1 -0
- package/dist/languages/structured-text/index.js +120 -0
- package/dist/languages/structured-text/index.js.map +1 -0
- package/dist/languages/tsx/index.d.ts +4 -0
- package/dist/languages/tsx/index.d.ts.map +1 -0
- package/dist/languages/tsx/index.js +7 -0
- package/dist/languages/tsx/index.js.map +1 -0
- package/dist/languages/typescript/index.d.ts +4 -0
- package/dist/languages/typescript/index.d.ts.map +1 -0
- package/dist/languages/typescript/index.js +7 -0
- package/dist/languages/typescript/index.js.map +1 -0
- package/dist/languages/vue/index.d.ts +21 -0
- package/dist/languages/vue/index.d.ts.map +1 -0
- package/dist/languages/vue/index.js +234 -0
- package/dist/languages/vue/index.js.map +1 -0
- package/dist/languages/vue/mapping.d.ts +11 -0
- package/dist/languages/vue/mapping.d.ts.map +1 -0
- package/dist/languages/vue/mapping.js +77 -0
- package/dist/languages/vue/mapping.js.map +1 -0
- package/dist/registry.d.ts +24 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +42 -0
- package/dist/registry.js.map +1 -0
- package/dist/text/byte-offset-table.d.ts +27 -0
- package/dist/text/byte-offset-table.d.ts.map +1 -0
- package/dist/text/byte-offset-table.js +39 -0
- package/dist/text/byte-offset-table.js.map +1 -0
- package/package.json +125 -3
- package/src/boundaries/expression.ts +95 -0
- package/src/boundaries/extract.ts +307 -0
- package/src/boundaries/python.ts +181 -0
- package/src/index.ts +51 -0
- package/src/languages/__golden__/javascript-basic.json +32 -0
- package/src/languages/__golden__/tsx-basic.json +19 -0
- package/src/languages/__golden__/typescript-basic.json +37 -0
- package/src/languages/astgrep-backend/GRAMMARS.md +86 -0
- package/src/languages/astgrep-backend/README.md +112 -0
- package/src/languages/astgrep-backend/__golden__/bash-basic.sh +53 -0
- package/src/languages/astgrep-backend/__golden__/c-basic.c +64 -0
- package/src/languages/astgrep-backend/__golden__/cpp-basic.cpp +65 -0
- package/src/languages/astgrep-backend/__golden__/csharp-basic.cs +73 -0
- package/src/languages/astgrep-backend/__golden__/go-basic.go +93 -0
- package/src/languages/astgrep-backend/__golden__/java-basic.java +62 -0
- package/src/languages/astgrep-backend/__golden__/kotlin-basic.kt +58 -0
- package/src/languages/astgrep-backend/__golden__/php-basic.php +72 -0
- package/src/languages/astgrep-backend/__golden__/ruby-basic.rb +62 -0
- package/src/languages/astgrep-backend/__golden__/rust-basic.rs +82 -0
- package/src/languages/astgrep-backend/__golden__/swift-basic.swift +73 -0
- package/src/languages/astgrep-backend/adapter.ts +240 -0
- package/src/languages/astgrep-backend/index.ts +44 -0
- package/src/languages/astgrep-backend/lang/README.md +115 -0
- package/src/languages/astgrep-backend/lang/bash.ts +78 -0
- package/src/languages/astgrep-backend/lang/c.ts +119 -0
- package/src/languages/astgrep-backend/lang/cpp.ts +65 -0
- package/src/languages/astgrep-backend/lang/csharp.ts +125 -0
- package/src/languages/astgrep-backend/lang/index.ts +61 -0
- package/src/languages/astgrep-backend/lang/java.ts +115 -0
- package/src/languages/astgrep-backend/lang/kotlin.ts +136 -0
- package/src/languages/astgrep-backend/lang/php.ts +105 -0
- package/src/languages/astgrep-backend/lang/ruby.ts +105 -0
- package/src/languages/astgrep-backend/lang/swift.ts +118 -0
- package/src/languages/astgrep-backend/languages.ts +238 -0
- package/src/languages/astgrep-backend/napi.ts +261 -0
- package/src/languages/astgrep-backend/syntax.ts +242 -0
- package/src/languages/index.ts +53 -0
- package/src/languages/javascript/index.ts +9 -0
- package/src/languages/jsx/index.ts +9 -0
- package/src/languages/prose/index.ts +270 -0
- package/src/languages/prose/line-bytes.ts +52 -0
- package/src/languages/python/README.md +246 -0
- package/src/languages/python/__golden__/python-basic.json +44 -0
- package/src/languages/python/create-adapter.ts +29 -0
- package/src/languages/python/index.ts +8 -0
- package/src/languages/python/profile.ts +341 -0
- package/src/languages/python/signature.ts +183 -0
- package/src/languages/python/table.ts +135 -0
- package/src/languages/shared/__fixtures__/large-over-32kib.ts.txt +1207 -0
- package/src/languages/shared/call-detection.ts +112 -0
- package/src/languages/shared/chunks.ts +255 -0
- package/src/languages/shared/create-adapter.ts +136 -0
- package/src/languages/shared/decisions.ts +204 -0
- package/src/languages/shared/dialect.ts +55 -0
- package/src/languages/shared/doc-binding.ts +78 -0
- package/src/languages/shared/engine.ts +461 -0
- package/src/languages/shared/extract-symbols.ts +35 -0
- package/src/languages/shared/fingerprint.ts +99 -0
- package/src/languages/shared/grammar-loader.ts +67 -0
- package/src/languages/shared/import-detection.ts +154 -0
- package/src/languages/shared/js-family-profile.ts +246 -0
- package/src/languages/shared/node-kinds.ts +153 -0
- package/src/languages/shared/node-utils.ts +120 -0
- package/src/languages/shared/parse-diagnostics.ts +125 -0
- package/src/languages/shared/parse-limits.ts +147 -0
- package/src/languages/shared/profile.ts +184 -0
- package/src/languages/shared/scope-model.ts +43 -0
- package/src/languages/shared/scope-naming.ts +124 -0
- package/src/languages/shared/scope-signature.ts +108 -0
- package/src/languages/shared/signature.ts +149 -0
- package/src/languages/shared/syntax.ts +116 -0
- package/src/languages/shared/table-profile-decisions.ts +251 -0
- package/src/languages/shared/table-profile-module-facts.ts +177 -0
- package/src/languages/shared/table-profile-queries.ts +85 -0
- package/src/languages/shared/table-profile-scopes.ts +198 -0
- package/src/languages/shared/table-profile.ts +427 -0
- package/src/languages/shared/tree-sitter-types.ts +46 -0
- package/src/languages/structured-text/index.ts +192 -0
- package/src/languages/tsx/index.ts +9 -0
- package/src/languages/typescript/index.ts +9 -0
- package/src/languages/vue/index.ts +312 -0
- package/src/languages/vue/mapping.ts +93 -0
- package/src/registry.ts +61 -0
- package/src/text/byte-offset-table.ts +63 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call-site detection (architecture.md §6.5, spec.md §15.2). No resolution
|
|
3
|
+
* happens here — that is symbol-graph's job (task instructions, explicit).
|
|
4
|
+
*
|
|
5
|
+
* `require(...)` and dynamic `import(...)` are deliberately EXCLUDED here:
|
|
6
|
+
* they are modeled purely as `RawImport` entries (`import-detection.ts`) so
|
|
7
|
+
* the symbol-graph does not see the same source construct as both an import
|
|
8
|
+
* edge and a call edge.
|
|
9
|
+
*/
|
|
10
|
+
import type { RawCall } from "@untangle-code/domain";
|
|
11
|
+
|
|
12
|
+
import type { FileTextContext } from "./node-utils.js";
|
|
13
|
+
import { rangeOf } from "./node-utils.js";
|
|
14
|
+
import type { SyntaxNode } from "./syntax.js";
|
|
15
|
+
|
|
16
|
+
function argumentCountOf(node: SyntaxNode): number | undefined {
|
|
17
|
+
const args = node.childForFieldName("arguments");
|
|
18
|
+
return args !== null ? args.namedChildCount : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isRequireOrDynamicImportCallee(functionNode: SyntaxNode): boolean {
|
|
22
|
+
return (
|
|
23
|
+
functionNode.type === "import" ||
|
|
24
|
+
(functionNode.type === "identifier" && functionNode.text === "require")
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Detects a `call_expression` as a `RawCall`, or `null` for require/dynamic-import (see file doc). */
|
|
29
|
+
export function detectCallExpression(
|
|
30
|
+
node: SyntaxNode,
|
|
31
|
+
callerScopeId: string,
|
|
32
|
+
ctx: FileTextContext,
|
|
33
|
+
): RawCall | null {
|
|
34
|
+
const functionNode = node.childForFieldName("function");
|
|
35
|
+
if (functionNode === null) return null;
|
|
36
|
+
if (isRequireOrDynamicImportCallee(functionNode)) return null;
|
|
37
|
+
|
|
38
|
+
const range = rangeOf(node, ctx);
|
|
39
|
+
const argumentCount = argumentCountOf(node);
|
|
40
|
+
|
|
41
|
+
if (functionNode.type === "identifier") {
|
|
42
|
+
return {
|
|
43
|
+
calleeName: functionNode.text,
|
|
44
|
+
callerScopeId,
|
|
45
|
+
range,
|
|
46
|
+
...(argumentCount !== undefined ? { argumentCount } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (functionNode.type === "member_expression") {
|
|
51
|
+
const property = functionNode.childForFieldName("property");
|
|
52
|
+
const object = functionNode.childForFieldName("object");
|
|
53
|
+
if (property === null) return null;
|
|
54
|
+
return {
|
|
55
|
+
calleeName: property.text,
|
|
56
|
+
...(object !== null ? { qualifier: object.text } : {}),
|
|
57
|
+
callerScopeId,
|
|
58
|
+
range,
|
|
59
|
+
...(argumentCount !== undefined ? { argumentCount } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Any other callee shape (IIFE, call result invoked again, computed
|
|
64
|
+
// member, etc.): fall back to the raw written expression text as the
|
|
65
|
+
// callee name so the call site is not silently dropped.
|
|
66
|
+
return {
|
|
67
|
+
calleeName: functionNode.text,
|
|
68
|
+
callerScopeId,
|
|
69
|
+
range,
|
|
70
|
+
...(argumentCount !== undefined ? { argumentCount } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Detects a `new_expression` as a `RawCall` (constructor invocation). */
|
|
75
|
+
export function detectNewExpression(
|
|
76
|
+
node: SyntaxNode,
|
|
77
|
+
callerScopeId: string,
|
|
78
|
+
ctx: FileTextContext,
|
|
79
|
+
): RawCall | null {
|
|
80
|
+
const constructorNode = node.childForFieldName("constructor");
|
|
81
|
+
if (constructorNode === null) return null;
|
|
82
|
+
const range = rangeOf(node, ctx);
|
|
83
|
+
const args = node.childForFieldName("arguments");
|
|
84
|
+
const argumentCount = args !== null ? args.namedChildCount : undefined;
|
|
85
|
+
|
|
86
|
+
if (constructorNode.type === "identifier") {
|
|
87
|
+
return {
|
|
88
|
+
calleeName: constructorNode.text,
|
|
89
|
+
callerScopeId,
|
|
90
|
+
range,
|
|
91
|
+
...(argumentCount !== undefined ? { argumentCount } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (constructorNode.type === "member_expression") {
|
|
95
|
+
const property = constructorNode.childForFieldName("property");
|
|
96
|
+
const object = constructorNode.childForFieldName("object");
|
|
97
|
+
if (property === null) return null;
|
|
98
|
+
return {
|
|
99
|
+
calleeName: property.text,
|
|
100
|
+
...(object !== null ? { qualifier: object.text } : {}),
|
|
101
|
+
callerScopeId,
|
|
102
|
+
range,
|
|
103
|
+
...(argumentCount !== undefined ? { argumentCount } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
calleeName: constructorNode.text,
|
|
108
|
+
callerScopeId,
|
|
109
|
+
range,
|
|
110
|
+
...(argumentCount !== undefined ? { argumentCount } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AstRegion tree construction from the `ScopeNode` tree (spec.md §8.2-§8.3).
|
|
3
|
+
*
|
|
4
|
+
* One `AstRegion` of kind `"module"` is returned, spanning the whole file,
|
|
5
|
+
* with a nested `children` tree the chunker descends into for large symbols
|
|
6
|
+
* (spec.md §8.3 step 6) — see domain `analysis.ts` `AstRegion.children` doc.
|
|
7
|
+
*
|
|
8
|
+
* `ChunkKind` (domain `types/common.ts`, spec.md §8.2) has no `"namespace"`
|
|
9
|
+
* member; a TS `namespace`/`module` scope maps to `"module"` (documented
|
|
10
|
+
* WP-06 decision, README "Entscheidungen") since it is itself a nested
|
|
11
|
+
* module-like grouping of symbols.
|
|
12
|
+
*/
|
|
13
|
+
import type { AstRegion, ChunkKind, SourceRange } from "@untangle-code/domain";
|
|
14
|
+
import { UntangleError } from "@untangle-code/domain";
|
|
15
|
+
|
|
16
|
+
import type { FileTextContext } from "./node-utils.js";
|
|
17
|
+
import { rangeOf, unionRange } from "./node-utils.js";
|
|
18
|
+
import type { LanguageProfile } from "./profile.js";
|
|
19
|
+
import type { ScopeNode } from "./scope-model.js";
|
|
20
|
+
import type { SyntaxNode } from "./syntax.js";
|
|
21
|
+
|
|
22
|
+
function chunkKindOf(kind: ScopeNode["kind"]): ChunkKind {
|
|
23
|
+
switch (kind) {
|
|
24
|
+
case "function":
|
|
25
|
+
return "function";
|
|
26
|
+
case "method":
|
|
27
|
+
return "method";
|
|
28
|
+
case "constructor":
|
|
29
|
+
return "constructor";
|
|
30
|
+
case "class":
|
|
31
|
+
return "class_overview";
|
|
32
|
+
case "interface":
|
|
33
|
+
return "interface";
|
|
34
|
+
case "type":
|
|
35
|
+
return "type";
|
|
36
|
+
case "enum":
|
|
37
|
+
return "enum";
|
|
38
|
+
case "namespace":
|
|
39
|
+
case "file":
|
|
40
|
+
return "module";
|
|
41
|
+
case "property":
|
|
42
|
+
// Individual properties never reach here directly — see groupProperties.
|
|
43
|
+
return "property_group";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The full source span a symbol's chunk should carry: the scope node itself,
|
|
49
|
+
* widened to the wrapping `export` statement (`export function f ...` —
|
|
50
|
+
* the scope node is only the declaration, but the `export` keyword bytes
|
|
51
|
+
* belong to the symbol's chunk, not to an interstitial module gap) and to
|
|
52
|
+
* the bound doc/decorator range (spec.md §8.3 step 2).
|
|
53
|
+
*/
|
|
54
|
+
function ownRange(scope: ScopeNode, ctx: FileTextContext, profile: LanguageProfile): SourceRange {
|
|
55
|
+
let range = scope.range;
|
|
56
|
+
const parent = scope.node.parent;
|
|
57
|
+
if (parent !== null && profile.exportWrapperKinds.has(parent.type)) {
|
|
58
|
+
range = unionRange(rangeOf(parent, ctx), range);
|
|
59
|
+
}
|
|
60
|
+
return scope.docRange !== undefined ? unionRange(scope.docRange, range) : range;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Regionen fuer Inhalt, der KEINEN Scope oeffnet, aber Bytes traegt (JS/TS:
|
|
65
|
+
* `static { ... }` im Klassenrumpf). Welche Knoten das sind, weiss das
|
|
66
|
+
* Sprachprofil, nicht diese Datei — sonst stuende hier je Sprache eine weitere
|
|
67
|
+
* verdrahtete Knotenart. Ohne diese Regionen fielen ihre Bytes durch den
|
|
68
|
+
* Mitgliederlauf; spec.md §8.4 verbietet genau das (kein stiller Datenverlust).
|
|
69
|
+
*/
|
|
70
|
+
function extraMemberRegions(
|
|
71
|
+
scopeNode: SyntaxNode,
|
|
72
|
+
parentScopeId: string,
|
|
73
|
+
ctx: FileTextContext,
|
|
74
|
+
profile: LanguageProfile,
|
|
75
|
+
): AstRegion[] {
|
|
76
|
+
return profile.memberRegions(scopeNode, parentScopeId, ctx).map((member) => ({
|
|
77
|
+
chunkKind: "property_group" as const,
|
|
78
|
+
range: member.range,
|
|
79
|
+
scopeId: member.scopeId,
|
|
80
|
+
headerText: member.headerText,
|
|
81
|
+
splittable: false,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function toRegion(
|
|
86
|
+
scope: ScopeNode,
|
|
87
|
+
groupOrdinal: { next: number },
|
|
88
|
+
ctx: FileTextContext,
|
|
89
|
+
profile: LanguageProfile,
|
|
90
|
+
): AstRegion {
|
|
91
|
+
let children = buildChildRegions(scope.children, scope.scopeId, groupOrdinal, ctx, profile);
|
|
92
|
+
if (scope.kind === "class") {
|
|
93
|
+
const extras = extraMemberRegions(scope.node, scope.scopeId, ctx, profile);
|
|
94
|
+
if (extras.length > 0) {
|
|
95
|
+
children = [...children, ...extras].sort((a, b) => a.range.startByte - b.range.startByte);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const headerText = scope.signature ?? scope.normalizedSignature;
|
|
99
|
+
return {
|
|
100
|
+
chunkKind: chunkKindOf(scope.kind),
|
|
101
|
+
range: ownRange(scope, ctx, profile),
|
|
102
|
+
scopeId: scope.scopeId,
|
|
103
|
+
...(scope.qualifiedName.length > 0 ? { symbolFqn: scope.qualifiedName } : {}),
|
|
104
|
+
...(headerText !== undefined && headerText.length > 0 ? { headerText } : {}),
|
|
105
|
+
splittable: children.length > 0,
|
|
106
|
+
...(children.length > 0 ? { children } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Exported (in addition to being called from {@link buildChildRegions})
|
|
112
|
+
* so `chunks.test.ts` can exercise the WP-73 empty-list guard directly:
|
|
113
|
+
* `buildChildRegions`'s own `flushProperties` never calls this with an
|
|
114
|
+
* empty `properties` array, so that path cannot be reached end-to-end
|
|
115
|
+
* through `buildFileRegion` alone.
|
|
116
|
+
*/
|
|
117
|
+
export function propertyGroupRegion(
|
|
118
|
+
properties: ScopeNode[],
|
|
119
|
+
parentScopeId: string,
|
|
120
|
+
ordinal: number,
|
|
121
|
+
ctx: FileTextContext,
|
|
122
|
+
profile: LanguageProfile,
|
|
123
|
+
): AstRegion {
|
|
124
|
+
const [firstProperty, ...restProperties] = properties;
|
|
125
|
+
if (firstProperty === undefined) {
|
|
126
|
+
// WP-73: `properties` must never be empty — the only caller
|
|
127
|
+
// (`flushProperties`) already guards on `pendingProperties.length === 0`
|
|
128
|
+
// and never reaches this function otherwise. An empty list here means
|
|
129
|
+
// that invariant was violated by a future caller; there is no
|
|
130
|
+
// meaningful `SourceRange` for zero properties, so this is an honest
|
|
131
|
+
// internal-defect report (E4) rather than a defined empty-range value
|
|
132
|
+
// that would silently mask the bug, and rather than the bare
|
|
133
|
+
// `Array.prototype.reduce` `TypeError: Reduce of empty array with no
|
|
134
|
+
// initial value` a caller would otherwise see.
|
|
135
|
+
throw new UntangleError(
|
|
136
|
+
"ANALYSIS_INCOMPLETE",
|
|
137
|
+
"propertyGroupRegion called with an empty properties list — a property_group region requires at least one property (spec.md §8.3)",
|
|
138
|
+
{
|
|
139
|
+
retryable: false,
|
|
140
|
+
operation: "chunk",
|
|
141
|
+
context: { parentScopeId, ordinal },
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const range = restProperties.reduce(
|
|
146
|
+
(acc, property) => unionRange(acc, ownRange(property, ctx, profile)),
|
|
147
|
+
ownRange(firstProperty, ctx, profile),
|
|
148
|
+
);
|
|
149
|
+
return {
|
|
150
|
+
chunkKind: "property_group",
|
|
151
|
+
range,
|
|
152
|
+
scopeId: `${parentScopeId}#properties#${ordinal}`,
|
|
153
|
+
splittable: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Builds child regions for one scope, merging consecutive `property` children into `property_group`s. */
|
|
158
|
+
function buildChildRegions(
|
|
159
|
+
children: ScopeNode[],
|
|
160
|
+
parentScopeId: string,
|
|
161
|
+
groupOrdinal: { next: number },
|
|
162
|
+
ctx: FileTextContext,
|
|
163
|
+
profile: LanguageProfile,
|
|
164
|
+
): AstRegion[] {
|
|
165
|
+
const regions: AstRegion[] = [];
|
|
166
|
+
let pendingProperties: ScopeNode[] = [];
|
|
167
|
+
|
|
168
|
+
const flushProperties = (): void => {
|
|
169
|
+
if (pendingProperties.length === 0) return;
|
|
170
|
+
regions.push(
|
|
171
|
+
propertyGroupRegion(pendingProperties, parentScopeId, groupOrdinal.next, ctx, profile),
|
|
172
|
+
);
|
|
173
|
+
groupOrdinal.next += 1;
|
|
174
|
+
pendingProperties = [];
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
for (const child of children) {
|
|
178
|
+
if (child.kind === "property") {
|
|
179
|
+
pendingProperties.push(child);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
flushProperties();
|
|
183
|
+
regions.push(toRegion(child, groupOrdinal, ctx, profile));
|
|
184
|
+
}
|
|
185
|
+
flushProperties();
|
|
186
|
+
return regions;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Leading file content (shebang/header comments/imports) before the first top-level region, if non-trivial. */
|
|
190
|
+
/** Builds the single root ("module") `AstRegion` for a whole file. */
|
|
191
|
+
export function buildFileRegion(
|
|
192
|
+
fileScope: ScopeNode,
|
|
193
|
+
ctx: FileTextContext,
|
|
194
|
+
profile: LanguageProfile,
|
|
195
|
+
): AstRegion {
|
|
196
|
+
const groupOrdinal = { next: 0 };
|
|
197
|
+
const topLevelRegions = buildChildRegions(
|
|
198
|
+
fileScope.children,
|
|
199
|
+
fileScope.scopeId,
|
|
200
|
+
groupOrdinal,
|
|
201
|
+
ctx,
|
|
202
|
+
profile,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const firstRegion = topLevelRegions[0];
|
|
206
|
+
const preamble =
|
|
207
|
+
firstRegion !== undefined ? buildFilePreambleRegion(fileScope, firstRegion, ctx) : null;
|
|
208
|
+
const allChildren = preamble !== null ? [preamble, ...topLevelRegions] : topLevelRegions;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
chunkKind: "module",
|
|
212
|
+
range: fileScope.range,
|
|
213
|
+
scopeId: fileScope.scopeId,
|
|
214
|
+
splittable: allChildren.length > 0,
|
|
215
|
+
...(allChildren.length > 0 ? { children: allChildren } : {}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildFilePreambleRegion(
|
|
220
|
+
fileScope: ScopeNode,
|
|
221
|
+
firstChild: AstRegion,
|
|
222
|
+
ctx: FileTextContext,
|
|
223
|
+
): AstRegion | null {
|
|
224
|
+
if (firstChild.range.startByte <= fileScope.range.startByte) return null;
|
|
225
|
+
const rawPreamble = ctx.sourceText.slice(
|
|
226
|
+
0,
|
|
227
|
+
indexForByteOffsetApprox(ctx, firstChild.range.startByte),
|
|
228
|
+
);
|
|
229
|
+
if (rawPreamble.trim().length === 0) return null;
|
|
230
|
+
return {
|
|
231
|
+
chunkKind: "file_preamble",
|
|
232
|
+
range: {
|
|
233
|
+
startLine: fileScope.range.startLine,
|
|
234
|
+
endLine: firstChild.range.startLine,
|
|
235
|
+
startByte: fileScope.range.startByte,
|
|
236
|
+
endByte: firstChild.range.startByte,
|
|
237
|
+
},
|
|
238
|
+
scopeId: fileScope.scopeId,
|
|
239
|
+
splittable: false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Approximates a UTF-16 slice index for a byte offset by scanning the
|
|
244
|
+
// (small, preamble-sized) prefix — the preamble is always near the top of
|
|
245
|
+
// the file, so this is O(preamble length), not O(file length).
|
|
246
|
+
function indexForByteOffsetApprox(ctx: FileTextContext, byteOffset: number): number {
|
|
247
|
+
let low = 0;
|
|
248
|
+
let high = ctx.sourceText.length;
|
|
249
|
+
while (low < high) {
|
|
250
|
+
const mid = Math.floor((low + high) / 2);
|
|
251
|
+
if (ctx.byteTable.toByteOffset(mid) < byteOffset) low = mid + 1;
|
|
252
|
+
else high = mid;
|
|
253
|
+
}
|
|
254
|
+
return low;
|
|
255
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wires one `Dialect` config into a full `LanguageAdapter` (domain
|
|
3
|
+
* `analysis.ts`), backed by the shared tree-sitter engine.
|
|
4
|
+
*
|
|
5
|
+
* See `engine.ts`'s doc comment for why every extraction method is a
|
|
6
|
+
* projection over one memoized `AnalysisResult`.
|
|
7
|
+
*/
|
|
8
|
+
import type {
|
|
9
|
+
AstRegion,
|
|
10
|
+
ComplexityFacts,
|
|
11
|
+
HalsteadTokens,
|
|
12
|
+
LanguageAdapter,
|
|
13
|
+
ParsedAst,
|
|
14
|
+
ParseResult,
|
|
15
|
+
RawCall,
|
|
16
|
+
RawImport,
|
|
17
|
+
SourceFile,
|
|
18
|
+
} from "@untangle-code/domain";
|
|
19
|
+
|
|
20
|
+
import { buildByteOffsetTable } from "../../text/byte-offset-table.js";
|
|
21
|
+
|
|
22
|
+
import { buildFileRegion } from "./chunks.js";
|
|
23
|
+
import type { Dialect } from "./dialect.js";
|
|
24
|
+
import type { AnalysisResult } from "./engine.js";
|
|
25
|
+
import { analyzeTree } from "./engine.js";
|
|
26
|
+
import { extractedSymbolsOf } from "./extract-symbols.js";
|
|
27
|
+
import { loadGrammar, loadParserCtor } from "./grammar-loader.js";
|
|
28
|
+
import { createJsFamilyProfile } from "./js-family-profile.js";
|
|
29
|
+
import type { FileTextContext } from "./node-utils.js";
|
|
30
|
+
import { collectParseDiagnostics } from "./parse-diagnostics.js";
|
|
31
|
+
import { parseWithBuffer } from "./parse-limits.js";
|
|
32
|
+
import type { LanguageProfile } from "./profile.js";
|
|
33
|
+
import type { TsTree } from "./tree-sitter-types.js";
|
|
34
|
+
|
|
35
|
+
/** The opaque payload stored in `ParsedAst.root` by this package's `parse()`. */
|
|
36
|
+
interface TreeSitterAstRoot {
|
|
37
|
+
tree: TsTree;
|
|
38
|
+
ctx: FileTextContext;
|
|
39
|
+
fileId: string;
|
|
40
|
+
profile: LanguageProfile;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const analysisCache = new WeakMap<ParsedAst, AnalysisResult>();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* `ComplexityFacts.parseDiagnostics` ist der EINZIGE Kanal, ueber den
|
|
47
|
+
* nachgelagerte Rechnungen (`computeSyntactic`, Berichte, Skip-Ledger) davon
|
|
48
|
+
* erfahren, dass der Syntaxbaum einer Datei beschaedigt ist. `analyzeTree`
|
|
49
|
+
* sieht die Diagnosen nicht — sie entstehen in `parse()` und haengen am
|
|
50
|
+
* `ParsedAst`. Wurde das Feld hier nicht befuellt, blieb es AUSNAHMSLOS leer:
|
|
51
|
+
* `parse_error_node_count` und `missing_node_count` meldeten dann fuer jede
|
|
52
|
+
* Datei 0, auch fuer eine mit `parseQuality: "partial"` und 25 Fehlerknoten.
|
|
53
|
+
* Genau dieses stille Verschlucken verbietet E4.
|
|
54
|
+
*/
|
|
55
|
+
function getAnalysis(ast: ParsedAst): AnalysisResult {
|
|
56
|
+
const cached = analysisCache.get(ast);
|
|
57
|
+
if (cached !== undefined) return cached;
|
|
58
|
+
const root = ast.root as TreeSitterAstRoot;
|
|
59
|
+
const analyzed = analyzeTree(root.tree.rootNode, root.ctx, root.fileId, root.profile);
|
|
60
|
+
const result: AnalysisResult = {
|
|
61
|
+
...analyzed,
|
|
62
|
+
facts: { ...analyzed.facts, parseDiagnostics: ast.diagnostics },
|
|
63
|
+
};
|
|
64
|
+
analysisCache.set(ast, result);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createTreeSitterAdapter(dialect: Dialect): LanguageAdapter {
|
|
69
|
+
// EIN Profil je Adapter, einmal gebaut: die Mengen darin sind unveraenderlich,
|
|
70
|
+
// und der Walk liest sie je Knoten (`engine.ts`).
|
|
71
|
+
const profile = createJsFamilyProfile(dialect);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
languageId: dialect.languageId,
|
|
75
|
+
extensions: dialect.extensions,
|
|
76
|
+
supportLevel: dialect.supportLevel,
|
|
77
|
+
|
|
78
|
+
parse(source: SourceFile): ParseResult {
|
|
79
|
+
const ParserCtor = loadParserCtor();
|
|
80
|
+
const language = loadGrammar(dialect.grammar);
|
|
81
|
+
const parser = new ParserCtor();
|
|
82
|
+
parser.setLanguage(language);
|
|
83
|
+
// NIE `parser.parse(source.content)` — ohne `bufferSize` wirft die native
|
|
84
|
+
// Bindung fuer jede Datei ueber 32 KiB `Invalid argument` (WP-215).
|
|
85
|
+
// `parseWithBuffer` prueft die Groessengrenze, setzt den Puffer und
|
|
86
|
+
// benennt jede Ausnahme (WP-216).
|
|
87
|
+
const tree = parseWithBuffer(parser, source.content);
|
|
88
|
+
|
|
89
|
+
const byteTable = buildByteOffsetTable(source.content);
|
|
90
|
+
const ctx: FileTextContext = { sourceText: source.content, byteTable };
|
|
91
|
+
const { parseQuality, diagnostics, errorNodeCount, missingNodeCount } =
|
|
92
|
+
collectParseDiagnostics(tree.rootNode, ctx);
|
|
93
|
+
|
|
94
|
+
if (parseQuality === "failed") {
|
|
95
|
+
return { parseQuality, ast: null, diagnostics, errorNodeCount, missingNodeCount };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const root: TreeSitterAstRoot = { tree, ctx, fileId: source.fileId, profile };
|
|
99
|
+
return {
|
|
100
|
+
parseQuality,
|
|
101
|
+
ast: { languageId: dialect.languageId, parseQuality, root, diagnostics },
|
|
102
|
+
diagnostics,
|
|
103
|
+
errorNodeCount,
|
|
104
|
+
missingNodeCount,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
extractChunks(ast): AstRegion[] {
|
|
109
|
+
const { scopeTree } = getAnalysis(ast);
|
|
110
|
+
const root = ast.root as TreeSitterAstRoot;
|
|
111
|
+
return [buildFileRegion(scopeTree, root.ctx, root.profile)];
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
extractSymbols(ast) {
|
|
115
|
+
const { scopeTree } = getAnalysis(ast);
|
|
116
|
+
return extractedSymbolsOf(scopeTree);
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
extractImports(ast): RawImport[] {
|
|
120
|
+
return getAnalysis(ast).imports;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
extractCalls(ast): RawCall[] {
|
|
124
|
+
return getAnalysis(ast).calls;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
computeComplexity(ast): ComplexityFacts {
|
|
128
|
+
return getAnalysis(ast).facts;
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
tokenizeForHalstead(ast): HalsteadTokens {
|
|
132
|
+
const { facts } = getAnalysis(ast);
|
|
133
|
+
return { operators: facts.operators, operands: facts.operands };
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision-point and nesting-event detection (spec.md §16.2-§16.4).
|
|
3
|
+
*
|
|
4
|
+
* Called once per non-leaf node during the main walk (`engine.ts`); returns
|
|
5
|
+
* the zero-or-one `DecisionFact`/`NestingFact` pair this specific node
|
|
6
|
+
* contributes (attribution to a `symbolScopeId` happens in the caller,
|
|
7
|
+
* which knows the current scope).
|
|
8
|
+
*/
|
|
9
|
+
import type { DecisionKind } from "@untangle-code/domain";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
BINARY_EXPRESSION,
|
|
13
|
+
CATCH_CLAUSE,
|
|
14
|
+
IF_STATEMENT,
|
|
15
|
+
JUMP_STATEMENT_TYPES,
|
|
16
|
+
LOOP_TYPES,
|
|
17
|
+
SWITCH_CASE,
|
|
18
|
+
SWITCH_STATEMENT,
|
|
19
|
+
TERNARY_EXPRESSION,
|
|
20
|
+
} from "./node-kinds.js";
|
|
21
|
+
import type { SyntaxNode } from "./syntax.js";
|
|
22
|
+
|
|
23
|
+
export interface DetectedDecision {
|
|
24
|
+
kind: DecisionKind;
|
|
25
|
+
countsForClassic: boolean;
|
|
26
|
+
countsForExtended: boolean;
|
|
27
|
+
logicalGroupId?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Wie oft dieser EINE Knoten die Entscheidung beitraegt (Vorgabe: 1).
|
|
30
|
+
*
|
|
31
|
+
* WOFUER: eine Grammatik kann eine n-stellige Alternative FLACH ablegen.
|
|
32
|
+
* Rusts `or_pattern` ist linksrekursiv — `A | B | C` sind zwei
|
|
33
|
+
* verschachtelte Knoten, also zwei Entscheidungen ohne weiteres Zutun.
|
|
34
|
+
* Pythons `union_pattern` ist FLACH: `case 1 | 2 | 3` ist EIN Knoten mit
|
|
35
|
+
* drei Zweigen. Ohne diese Zahl kostete `1 | 2 | 3` genauso viel wie
|
|
36
|
+
* `1 | 2`, und die Metrik behauptete eine Gleichheit, die es nicht gibt.
|
|
37
|
+
*
|
|
38
|
+
* Die zusaetzlichen Entscheidungen teilen sich den Bereich des Knotens:
|
|
39
|
+
* die Alternativen liegen darin, und ein erfundener engerer Bereich waere
|
|
40
|
+
* eine Angabe, die der Baum nicht hergibt.
|
|
41
|
+
*/
|
|
42
|
+
repeat?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DetectedNesting {
|
|
46
|
+
construct: string;
|
|
47
|
+
continuation: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DecisionDetectionResult {
|
|
51
|
+
decision?: DetectedDecision;
|
|
52
|
+
nesting?: DetectedNesting;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isElseIf(node: SyntaxNode): boolean {
|
|
56
|
+
return node.parent !== null && node.parent.type === "else_clause";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A guard clause: `if (cond) return/throw/break/continue;` with no `else`. */
|
|
60
|
+
function isGuardClause(node: SyntaxNode): boolean {
|
|
61
|
+
if (node.childForFieldName("alternative") !== null) return false;
|
|
62
|
+
const consequence = node.childForFieldName("consequence");
|
|
63
|
+
if (consequence === null) return false;
|
|
64
|
+
const soleStatement =
|
|
65
|
+
consequence.type === "statement_block"
|
|
66
|
+
? consequence.namedChildCount === 1
|
|
67
|
+
? consequence.namedChild(0)
|
|
68
|
+
: null
|
|
69
|
+
: consequence;
|
|
70
|
+
return soleStatement !== null && JUMP_STATEMENT_TYPES.has(soleStatement.type);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Finds the topmost node in a maximal chain of the same binary operator (for
|
|
75
|
+
* grouping §16.3/§16.4) and derives a stable id for it from its position in
|
|
76
|
+
* the source (`startIndex`, the group top node's byte offset) rather than
|
|
77
|
+
* tree-sitter's runtime `node.id`. `node.id` is a native-binding identifier
|
|
78
|
+
* (effectively a heap address) that is NOT stable across processes or even
|
|
79
|
+
* across repeated parses of the identical source within the same process —
|
|
80
|
+
* two parse runs of the same file can and do produce different `node.id`
|
|
81
|
+
* values. `startIndex` is deterministic: the same source byte range always
|
|
82
|
+
* yields the same id, which is what vision.md §8.5's determinism principle
|
|
83
|
+
* requires once `ComplexityFacts` are persisted or compared across runs.
|
|
84
|
+
*/
|
|
85
|
+
function logicalGroupIdFor(node: SyntaxNode, operatorText: string): string {
|
|
86
|
+
let top = node;
|
|
87
|
+
while (top.parent !== null && top.parent.type === BINARY_EXPRESSION) {
|
|
88
|
+
const parentOperator = top.parent.childForFieldName("operator")?.text;
|
|
89
|
+
if (parentOperator !== operatorText) break;
|
|
90
|
+
top = top.parent;
|
|
91
|
+
}
|
|
92
|
+
return `logical-${top.startIndex}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Die Knotentypen, fuer die {@link detectDecisionAndNesting} ueberhaupt etwas
|
|
97
|
+
* anderes als `{}` liefern kann — abgeleitet aus den Verzweigungen unten und
|
|
98
|
+
* durch `decisions.test.ts` an sie gebunden.
|
|
99
|
+
*
|
|
100
|
+
* WOZU: der Cursor-Walk (`engine.ts`, WP-95) materialisiert einen
|
|
101
|
+
* `SyntaxNode`-Wrapper nur dann, wenn ein Detektor ihn wirklich braucht. Ohne
|
|
102
|
+
* dieses Vorsieb muesste er fuer JEDEN inneren Knoten einen Wrapper bauen —
|
|
103
|
+
* also genau die Grenzueberquerung wieder einfuehren, die der Cursor spart.
|
|
104
|
+
*/
|
|
105
|
+
const DECISION_CANDIDATE_TYPES: ReadonlySet<string> = new Set([
|
|
106
|
+
IF_STATEMENT,
|
|
107
|
+
...LOOP_TYPES,
|
|
108
|
+
CATCH_CLAUSE,
|
|
109
|
+
TERNARY_EXPRESSION,
|
|
110
|
+
SWITCH_STATEMENT,
|
|
111
|
+
SWITCH_CASE,
|
|
112
|
+
BINARY_EXPRESSION,
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/** Kann ein Knoten dieses Typs eine Entscheidung oder ein Nesting-Ereignis tragen? */
|
|
116
|
+
export function mayCarryDecisionOrNesting(nodeType: string): boolean {
|
|
117
|
+
return DECISION_CANDIDATE_TYPES.has(nodeType);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function detectDecisionAndNesting(node: SyntaxNode): DecisionDetectionResult {
|
|
121
|
+
if (node.type === IF_STATEMENT) {
|
|
122
|
+
if (isElseIf(node)) {
|
|
123
|
+
return {
|
|
124
|
+
decision: { kind: "else_if", countsForClassic: true, countsForExtended: true },
|
|
125
|
+
nesting: { construct: "if", continuation: true },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const kind: DecisionKind = isGuardClause(node) ? "guard" : "if";
|
|
129
|
+
return {
|
|
130
|
+
decision: { kind, countsForClassic: true, countsForExtended: true },
|
|
131
|
+
nesting: { construct: "if", continuation: false },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (LOOP_TYPES.has(node.type)) {
|
|
136
|
+
return {
|
|
137
|
+
decision: { kind: "loop", countsForClassic: true, countsForExtended: true },
|
|
138
|
+
nesting: { construct: "loop", continuation: false },
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (node.type === CATCH_CLAUSE) {
|
|
143
|
+
return {
|
|
144
|
+
decision: { kind: "catch", countsForClassic: true, countsForExtended: true },
|
|
145
|
+
nesting: { construct: "catch", continuation: false },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (node.type === TERNARY_EXPRESSION) {
|
|
150
|
+
return {
|
|
151
|
+
decision: { kind: "ternary", countsForClassic: true, countsForExtended: true },
|
|
152
|
+
nesting: { construct: "ternary", continuation: false },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (node.type === SWITCH_STATEMENT) {
|
|
157
|
+
return { nesting: { construct: "switch", continuation: false } };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (node.type === SWITCH_CASE) {
|
|
161
|
+
return { decision: { kind: "case", countsForClassic: true, countsForExtended: true } };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (node.type === BINARY_EXPRESSION) {
|
|
165
|
+
const operator = node.childForFieldName("operator")?.text;
|
|
166
|
+
if (operator === "&&") {
|
|
167
|
+
return {
|
|
168
|
+
decision: {
|
|
169
|
+
kind: "logical_and",
|
|
170
|
+
countsForClassic: false,
|
|
171
|
+
countsForExtended: true,
|
|
172
|
+
logicalGroupId: logicalGroupIdFor(node, "&&"),
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (operator === "||") {
|
|
177
|
+
return {
|
|
178
|
+
decision: {
|
|
179
|
+
kind: "logical_or",
|
|
180
|
+
countsForClassic: false,
|
|
181
|
+
countsForExtended: true,
|
|
182
|
+
logicalGroupId: logicalGroupIdFor(node, "||"),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (operator === "??") {
|
|
187
|
+
return {
|
|
188
|
+
decision: {
|
|
189
|
+
kind: "short_circuit",
|
|
190
|
+
countsForClassic: false,
|
|
191
|
+
countsForExtended: true,
|
|
192
|
+
logicalGroupId: logicalGroupIdFor(node, "??"),
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Nesting construct for a lambda/local-function boundary (spec.md §16.4). */
|
|
202
|
+
export function lambdaNesting(): DetectedNesting {
|
|
203
|
+
return { construct: "lambda", continuation: false };
|
|
204
|
+
}
|