@liendev/parser 0.45.0 → 0.47.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/README.md +6 -2
- package/dist/ast/extractors/symbol-helpers.d.ts.map +1 -1
- package/dist/ast/extractors/symbol-helpers.js +23 -6
- package/dist/ast/extractors/symbol-helpers.js.map +1 -1
- package/dist/ast/languages/kotlin.d.ts +83 -0
- package/dist/ast/languages/kotlin.d.ts.map +1 -0
- package/dist/ast/languages/kotlin.js +506 -0
- package/dist/ast/languages/kotlin.js.map +1 -0
- package/dist/ast/languages/registry.d.ts +1 -1
- package/dist/ast/languages/registry.d.ts.map +1 -1
- package/dist/ast/languages/registry.js +6 -0
- package/dist/ast/languages/registry.js.map +1 -1
- package/dist/ast/languages/ruby.d.ts +80 -0
- package/dist/ast/languages/ruby.d.ts.map +1 -0
- package/dist/ast/languages/ruby.js +377 -0
- package/dist/ast/languages/ruby.js.map +1 -0
- package/dist/ast/symbols.d.ts.map +1 -1
- package/dist/ast/symbols.js +28 -7
- package/dist/ast/symbols.js.map +1 -1
- package/dist/symbol-extractor.d.ts.map +1 -1
- package/dist/symbol-extractor.js +45 -0
- package/dist/symbol-extractor.js.map +1 -1
- package/dist/utils/path-matching.d.ts +3 -1
- package/dist/utils/path-matching.d.ts.map +1 -1
- package/dist/utils/path-matching.js +6 -2
- package/dist/utils/path-matching.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This package provides the core parsing and analysis capabilities used by Lien's
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **AST Parsing** — Tree-sitter-based parsing for TypeScript, JavaScript, Python, PHP, Rust, and
|
|
9
|
+
- **AST Parsing** — Tree-sitter-based parsing for TypeScript, JavaScript, Python, PHP, Rust, Go, Java, C#, Ruby, and Kotlin
|
|
10
10
|
- **Semantic Chunking** — Split code into meaningful chunks respecting function/class boundaries
|
|
11
11
|
- **Complexity Analysis** — Cyclomatic, cognitive, and Halstead complexity metrics
|
|
12
12
|
- **Dependency Analysis** — Import/export tracking with transitive dependent resolution
|
|
@@ -24,8 +24,12 @@ This package provides the core parsing and analysis capabilities used by Lien's
|
|
|
24
24
|
| PHP | Yes | Yes | Yes |
|
|
25
25
|
| Rust | Yes | Yes | Yes |
|
|
26
26
|
| Go | Yes | Yes | Yes |
|
|
27
|
+
| Java | Yes | Yes | Yes |
|
|
28
|
+
| C# | Yes | Yes | Yes |
|
|
29
|
+
| Ruby | Yes | Yes | Yes |
|
|
30
|
+
| Kotlin | Yes | Yes | Yes |
|
|
27
31
|
|
|
28
|
-
Line-based chunking and symbol extraction are available for additional languages including
|
|
32
|
+
Line-based chunking and symbol extraction are available for additional languages including Vue, Liquid, C/C++, Swift, Scala, and Markdown.
|
|
29
33
|
|
|
30
34
|
## Usage
|
|
31
35
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbol-helpers.d.ts","sourceRoot":"","sources":["../../../src/ast/extractors/symbol-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"symbol-helpers.d.ts","sourceRoot":"","sources":["../../../src/ast/extractors/symbol-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAqCjF;AASD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAerF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK/F"}
|
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
* Extract function/method signature
|
|
3
3
|
*/
|
|
4
4
|
export function extractSignature(node, content) {
|
|
5
|
-
//
|
|
5
|
+
// Preferred: bound the signature by where the function body begins. This is
|
|
6
|
+
// language-agnostic — it works for brace languages (`… ) {`), colon languages
|
|
7
|
+
// (Python `… ):`), and `end` languages (Ruby `def … )`). The legacy brace/arrow
|
|
8
|
+
// scan below walked an entire no-brace body into the "signature" (e.g. Python,
|
|
9
|
+
// Ruby), which this avoids.
|
|
10
|
+
const bodyNode = node.childForFieldName('body');
|
|
11
|
+
if (bodyNode) {
|
|
12
|
+
const signature = content
|
|
13
|
+
.slice(node.startIndex, bodyNode.startIndex)
|
|
14
|
+
.replace(/\s+/g, ' ') // collapse newlines/indentation (matches the old join-with-space)
|
|
15
|
+
.replace(/(\{|=>|:)\s*$/, '') // drop a dangling block-opener if it was captured
|
|
16
|
+
.trim();
|
|
17
|
+
return clampSignatureLength(signature);
|
|
18
|
+
}
|
|
19
|
+
// Fallback: nodes with no `body` field (e.g. Rust trait signatures, abstract
|
|
20
|
+
// interface methods). Preserve the original first-line / brace-scan behavior.
|
|
6
21
|
const startLine = node.startPosition.row;
|
|
7
22
|
const lines = content.split('\n');
|
|
8
23
|
let signature = lines[startLine] || '';
|
|
@@ -16,11 +31,13 @@ export function extractSignature(node, content) {
|
|
|
16
31
|
}
|
|
17
32
|
// Clean up signature
|
|
18
33
|
signature = signature.split('{')[0].split('=>')[0].trim();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
return clampSignatureLength(signature);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Truncate an over-long signature to keep stored chunks compact.
|
|
38
|
+
*/
|
|
39
|
+
function clampSignatureLength(signature) {
|
|
40
|
+
return signature.length > 200 ? signature.substring(0, 197) + '...' : signature;
|
|
24
41
|
}
|
|
25
42
|
/**
|
|
26
43
|
* Extract parameter list from function node
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbol-helpers.js","sourceRoot":"","sources":["../../../src/ast/extractors/symbol-helpers.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAuB,EAAE,OAAe;IACvE,
|
|
1
|
+
{"version":3,"file":"symbol-helpers.js","sourceRoot":"","sources":["../../../src/ast/extractors/symbol-helpers.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAuB,EAAE,OAAe;IACvE,4EAA4E;IAC5E,8EAA8E;IAC9E,gFAAgF;IAChF,+EAA+E;IAC/E,4BAA4B;IAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,SAAS,GAAG,OAAO;aACtB,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC;aAC3C,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,kEAAkE;aACvF,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,kDAAkD;aAC/E,IAAI,EAAE,CAAC;QACV,OAAO,oBAAoB,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAEvC,wEAAwE;IACxE,IAAI,WAAW,GAAG,SAAS,CAAC;IAC5B,OACE,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG;QAClC,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QACxB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EACzB,CAAC;QACD,WAAW,EAAE,CAAC;QACd,SAAS,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,qBAAqB;IACrB,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAE1D,OAAO,oBAAoB,CAAC,SAAS,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,SAAiB;IAC7C,OAAO,SAAS,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAClF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAuB,EAAE,QAAgB;IACzE,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,uBAAuB;IACvB,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU;QAAE,OAAO,UAAU,CAAC;IAEnC,2BAA2B;IAC3B,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,aAAa,EAAE,CAAC;QAC7C,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACtB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAuB,EAAE,QAAgB;IACzE,MAAM,cAAc,GAAG,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC;IAC7D,IAAI,CAAC,cAAc;QAAE,OAAO,SAAS,CAAC;IAEtC,OAAO,cAAc,CAAC,IAAI,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
import type { SymbolInfo } from '../types.js';
|
|
3
|
+
import type { LanguageDefinition } from './types.js';
|
|
4
|
+
import type { LanguageTraverser, DeclarationFunctionInfo } from '../traversers/types.js';
|
|
5
|
+
import type { LanguageExportExtractor, LanguageImportExtractor, LanguageSymbolExtractor } from '../extractors/types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Kotlin AST traverser.
|
|
8
|
+
*
|
|
9
|
+
* Methods live inside `class_declaration` / `object_declaration` bodies
|
|
10
|
+
* (`class_body` / `enum_class_body`). `companion_object` is transparent — its
|
|
11
|
+
* members are traversed so their methods are captured (attributed to the
|
|
12
|
+
* enclosing class). Lambdas can appear in property initializers
|
|
13
|
+
* (`val f = { … }`).
|
|
14
|
+
*/
|
|
15
|
+
export declare class KotlinTraverser implements LanguageTraverser {
|
|
16
|
+
targetNodeTypes: string[];
|
|
17
|
+
containerTypes: string[];
|
|
18
|
+
declarationTypes: string[];
|
|
19
|
+
functionTypes: string[];
|
|
20
|
+
shouldExtractChildren(node: Parser.SyntaxNode): boolean;
|
|
21
|
+
isDeclarationWithFunction(node: Parser.SyntaxNode): boolean;
|
|
22
|
+
getContainerBody(node: Parser.SyntaxNode): Parser.SyntaxNode | null;
|
|
23
|
+
shouldTraverseChildren(node: Parser.SyntaxNode): boolean;
|
|
24
|
+
findParentContainerName(node: Parser.SyntaxNode): string | undefined;
|
|
25
|
+
findFunctionInDeclaration(node: Parser.SyntaxNode): DeclarationFunctionInfo;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Kotlin export extractor.
|
|
29
|
+
*
|
|
30
|
+
* Kotlin has no `export` keyword and is `public` by default — top-level
|
|
31
|
+
* declarations and (non-private/internal) members are importable. Interface
|
|
32
|
+
* members are implicitly public.
|
|
33
|
+
*/
|
|
34
|
+
export declare class KotlinExportExtractor implements LanguageExportExtractor {
|
|
35
|
+
extractExports(rootNode: Parser.SyntaxNode): string[];
|
|
36
|
+
private extractFromNode;
|
|
37
|
+
private extractMembers;
|
|
38
|
+
private propertyName;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Kotlin import extractor.
|
|
42
|
+
*
|
|
43
|
+
* Handles `import a.b.C`, wildcard `import a.b.*`, and aliased
|
|
44
|
+
* `import a.b.C as D`. The grammar nests `import_header` nodes inside an
|
|
45
|
+
* `import_list`; the engine's import scan descends one level to reach them
|
|
46
|
+
* (see `collectImportNodes` in ast/symbols.ts). Standard-library imports
|
|
47
|
+
* (kotlin.*, kotlinx.*, java.*, javax.*) are filtered out.
|
|
48
|
+
*/
|
|
49
|
+
export declare class KotlinImportExtractor implements LanguageImportExtractor {
|
|
50
|
+
readonly importNodeTypes: string[];
|
|
51
|
+
extractImportPath(node: Parser.SyntaxNode): string | null;
|
|
52
|
+
processImportSymbols(node: Parser.SyntaxNode): {
|
|
53
|
+
importPath: string;
|
|
54
|
+
symbols: string[];
|
|
55
|
+
} | null;
|
|
56
|
+
private getImportPath;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Kotlin symbol extractor.
|
|
60
|
+
*
|
|
61
|
+
* Handles `function_declaration`, `class_declaration` (class / interface / enum
|
|
62
|
+
* variants — distinguished by the keyword token), and `object_declaration`.
|
|
63
|
+
* `SymbolInfo.type` only has `function|method|class|interface`, so objects and
|
|
64
|
+
* enums map to `class` (keeping the keyword in the signature).
|
|
65
|
+
*
|
|
66
|
+
* Call sites: `call_expression` — `foo()` (simple_identifier) and `a.b.c()`
|
|
67
|
+
* (the member is the `simple_identifier` inside the trailing `navigation_suffix`).
|
|
68
|
+
*/
|
|
69
|
+
export declare class KotlinSymbolExtractor implements LanguageSymbolExtractor {
|
|
70
|
+
readonly symbolNodeTypes: string[];
|
|
71
|
+
extractSymbol(node: Parser.SyntaxNode, content: string, parentClass?: string): SymbolInfo | null;
|
|
72
|
+
extractCallSite(node: Parser.SyntaxNode): {
|
|
73
|
+
symbol: string;
|
|
74
|
+
line: number;
|
|
75
|
+
key: string;
|
|
76
|
+
} | null;
|
|
77
|
+
private extractFunctionInfo;
|
|
78
|
+
private extractClassInfo;
|
|
79
|
+
private extractObjectInfo;
|
|
80
|
+
private makeSymbol;
|
|
81
|
+
}
|
|
82
|
+
export declare const kotlinDefinition: LanguageDefinition;
|
|
83
|
+
//# sourceMappingURL=kotlin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kotlin.d.ts","sourceRoot":"","sources":["../../../src/ast/languages/kotlin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACzF,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AA+HhC;;;;;;;;GAQG;AACH,qBAAa,eAAgB,YAAW,iBAAiB;IACvD,eAAe,WAA4B;IAE3C,cAAc,WAA+C;IAE7D,gBAAgB,WAA4B;IAE5C,aAAa,WAA4C;IAEzD,qBAAqB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO;IAIvD,yBAAyB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO;IAI3D,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,GAAG,IAAI;IAKnE,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO;IASxD,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,SAAS;IAWpE,yBAAyB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG,uBAAuB;CAS5E;AAMD;;;;;;GAMG;AACH,qBAAa,qBAAsB,YAAW,uBAAuB;IACnE,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,EAAE;IAgBrD,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,YAAY;CAKrB;AAmBD;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,YAAW,uBAAuB;IACnE,QAAQ,CAAC,eAAe,WAAqB;IAE7C,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI;IAMzD,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI;IAoB/F,OAAO,CAAC,aAAa;CAMtB;AAMD;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,YAAW,uBAAuB;IACnE,QAAQ,CAAC,eAAe,WAAuE;IAE/F,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAahG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAoB9F,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,UAAU;CAcnB;AAMD,eAAO,MAAM,gBAAgB,EAAE,kBAoH9B,CAAC"}
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import Kotlin from 'tree-sitter-kotlin';
|
|
2
|
+
import { calculateComplexity } from '../complexity/index.js';
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// HELPERS
|
|
5
|
+
//
|
|
6
|
+
// The tree-sitter-kotlin grammar (fwcd) does NOT assign field names, so unlike
|
|
7
|
+
// the Java definition we locate children by node TYPE rather than via
|
|
8
|
+
// `childForFieldName`. These helpers centralize that.
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/** First named child of a given type. */
|
|
11
|
+
function childByType(node, type) {
|
|
12
|
+
return node.namedChildren.find(child => child.type === type) ?? null;
|
|
13
|
+
}
|
|
14
|
+
/** Whether a node has a child token of a given type (incl. anonymous keyword tokens). */
|
|
15
|
+
function hasTokenChild(node, type) {
|
|
16
|
+
return node.children.some(child => child.type === type);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The function-valued initializer of a property, if the property's initializer
|
|
20
|
+
* is *directly* a lambda or anonymous function (`val f = { … }`). Checks the
|
|
21
|
+
* initializer (the last named child) rather than any descendant, so a lambda
|
|
22
|
+
* passed as a call argument (`val n = xs.count { it > 0 }`) is NOT treated as a
|
|
23
|
+
* function-valued property.
|
|
24
|
+
*/
|
|
25
|
+
function propertyInitializerFunction(node) {
|
|
26
|
+
const initializer = node.namedChildren.at(-1);
|
|
27
|
+
if (!initializer)
|
|
28
|
+
return null;
|
|
29
|
+
return initializer.type === 'lambda_literal' || initializer.type === 'anonymous_function'
|
|
30
|
+
? initializer
|
|
31
|
+
: null;
|
|
32
|
+
}
|
|
33
|
+
/** The `function_body` child of a function_declaration ({ … } block OR `= expr`). */
|
|
34
|
+
function functionBody(node) {
|
|
35
|
+
return childByType(node, 'function_body');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The function name: the first direct `simple_identifier` child of a
|
|
39
|
+
* function_declaration. (Extension-function receivers are `user_type` nodes, so
|
|
40
|
+
* the bare simple_identifier is the name in the common case.)
|
|
41
|
+
*/
|
|
42
|
+
function functionName(node) {
|
|
43
|
+
return childByType(node, 'simple_identifier')?.text;
|
|
44
|
+
}
|
|
45
|
+
/** The declared name of a class/object declaration (`type_identifier`). */
|
|
46
|
+
function declarationName(node) {
|
|
47
|
+
return childByType(node, 'type_identifier')?.text;
|
|
48
|
+
}
|
|
49
|
+
const SIGNATURE_MAX = 200;
|
|
50
|
+
function clamp(signature) {
|
|
51
|
+
return signature.length > SIGNATURE_MAX
|
|
52
|
+
? signature.slice(0, SIGNATURE_MAX - 3) + '...'
|
|
53
|
+
: signature;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Function signature, bounded by where the body begins. `function_body` covers
|
|
57
|
+
* both block bodies (`{ … }`) and expression bodies (`= expr`, which starts at
|
|
58
|
+
* the `=`), so slicing up to its start yields a clean `fun foo(a: Int): Int`
|
|
59
|
+
* for both. Abstract/interface methods have no body — use the whole node.
|
|
60
|
+
*/
|
|
61
|
+
function functionSignature(node, content) {
|
|
62
|
+
const body = functionBody(node);
|
|
63
|
+
const end = body ? body.startIndex : node.endIndex;
|
|
64
|
+
const signature = content
|
|
65
|
+
.slice(node.startIndex, end)
|
|
66
|
+
.replace(/\s+/g, ' ')
|
|
67
|
+
.replace(/(\{|=)\s*$/, '') // drop a trailing block-opener / expression `=` if captured
|
|
68
|
+
.trim();
|
|
69
|
+
return clamp(signature);
|
|
70
|
+
}
|
|
71
|
+
/** Parameter texts from a function's `function_value_parameters`. */
|
|
72
|
+
function functionParameters(node) {
|
|
73
|
+
const params = childByType(node, 'function_value_parameters');
|
|
74
|
+
if (!params)
|
|
75
|
+
return [];
|
|
76
|
+
return params.namedChildren.filter(p => p.text.trim()).map(p => p.text);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Return type: the type node that sits between the parameter list and the body.
|
|
80
|
+
* Kotlin's grammar emits it as `user_type` / `nullable_type` / `function_type`.
|
|
81
|
+
*/
|
|
82
|
+
function functionReturnType(node) {
|
|
83
|
+
const children = node.namedChildren;
|
|
84
|
+
const paramsIndex = children.findIndex(c => c.type === 'function_value_parameters');
|
|
85
|
+
if (paramsIndex === -1)
|
|
86
|
+
return undefined;
|
|
87
|
+
for (let i = paramsIndex + 1; i < children.length; i++) {
|
|
88
|
+
const c = children[i];
|
|
89
|
+
if (c.type === 'function_body')
|
|
90
|
+
break;
|
|
91
|
+
if (c.type === 'user_type' || c.type === 'nullable_type' || c.type === 'function_type') {
|
|
92
|
+
return c.text;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
/** Visibility modifier text (`private` / `internal` / `protected` / `public`), if any. */
|
|
98
|
+
function visibilityModifier(node) {
|
|
99
|
+
const modifiers = childByType(node, 'modifiers');
|
|
100
|
+
if (!modifiers)
|
|
101
|
+
return null;
|
|
102
|
+
return modifiers.namedChildren.find(c => c.type === 'visibility_modifier')?.text ?? null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Kotlin declarations are `public` by default. We treat a declaration as
|
|
106
|
+
* "exported" (importable / part of the API) unless it is explicitly `private`
|
|
107
|
+
* or `internal`. `protected` members stay visible to subclasses, so we keep
|
|
108
|
+
* them. (Inverse of Java's "has a `public` modifier" check.)
|
|
109
|
+
*/
|
|
110
|
+
function isExported(node) {
|
|
111
|
+
const visibility = visibilityModifier(node);
|
|
112
|
+
return visibility !== 'private' && visibility !== 'internal';
|
|
113
|
+
}
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// TRAVERSER
|
|
116
|
+
// =============================================================================
|
|
117
|
+
/**
|
|
118
|
+
* Kotlin AST traverser.
|
|
119
|
+
*
|
|
120
|
+
* Methods live inside `class_declaration` / `object_declaration` bodies
|
|
121
|
+
* (`class_body` / `enum_class_body`). `companion_object` is transparent — its
|
|
122
|
+
* members are traversed so their methods are captured (attributed to the
|
|
123
|
+
* enclosing class). Lambdas can appear in property initializers
|
|
124
|
+
* (`val f = { … }`).
|
|
125
|
+
*/
|
|
126
|
+
export class KotlinTraverser {
|
|
127
|
+
targetNodeTypes = ['function_declaration'];
|
|
128
|
+
containerTypes = ['class_declaration', 'object_declaration'];
|
|
129
|
+
declarationTypes = ['property_declaration'];
|
|
130
|
+
functionTypes = ['lambda_literal', 'anonymous_function'];
|
|
131
|
+
shouldExtractChildren(node) {
|
|
132
|
+
return this.containerTypes.includes(node.type);
|
|
133
|
+
}
|
|
134
|
+
isDeclarationWithFunction(node) {
|
|
135
|
+
return node.type === 'property_declaration' && propertyInitializerFunction(node) !== null;
|
|
136
|
+
}
|
|
137
|
+
getContainerBody(node) {
|
|
138
|
+
if (!this.containerTypes.includes(node.type))
|
|
139
|
+
return null;
|
|
140
|
+
return childByType(node, 'class_body') ?? childByType(node, 'enum_class_body');
|
|
141
|
+
}
|
|
142
|
+
shouldTraverseChildren(node) {
|
|
143
|
+
return (node.type === 'source_file' ||
|
|
144
|
+
node.type === 'class_body' ||
|
|
145
|
+
node.type === 'enum_class_body' ||
|
|
146
|
+
node.type === 'companion_object');
|
|
147
|
+
}
|
|
148
|
+
findParentContainerName(node) {
|
|
149
|
+
let current = node.parent;
|
|
150
|
+
while (current) {
|
|
151
|
+
if (current.type === 'class_declaration' || current.type === 'object_declaration') {
|
|
152
|
+
return declarationName(current);
|
|
153
|
+
}
|
|
154
|
+
current = current.parent;
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
findFunctionInDeclaration(node) {
|
|
159
|
+
if (node.type !== 'property_declaration') {
|
|
160
|
+
return { hasFunction: false, functionNode: null };
|
|
161
|
+
}
|
|
162
|
+
const fn = propertyInitializerFunction(node);
|
|
163
|
+
return fn
|
|
164
|
+
? { hasFunction: true, functionNode: fn }
|
|
165
|
+
: { hasFunction: false, functionNode: null };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// EXPORT EXTRACTOR
|
|
170
|
+
// =============================================================================
|
|
171
|
+
/**
|
|
172
|
+
* Kotlin export extractor.
|
|
173
|
+
*
|
|
174
|
+
* Kotlin has no `export` keyword and is `public` by default — top-level
|
|
175
|
+
* declarations and (non-private/internal) members are importable. Interface
|
|
176
|
+
* members are implicitly public.
|
|
177
|
+
*/
|
|
178
|
+
export class KotlinExportExtractor {
|
|
179
|
+
extractExports(rootNode) {
|
|
180
|
+
const exports = [];
|
|
181
|
+
const seen = new Set();
|
|
182
|
+
const addExport = (name) => {
|
|
183
|
+
if (name && !seen.has(name)) {
|
|
184
|
+
seen.add(name);
|
|
185
|
+
exports.push(name);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
rootNode.namedChildren.forEach(child => this.extractFromNode(child, addExport));
|
|
189
|
+
return exports;
|
|
190
|
+
}
|
|
191
|
+
extractFromNode(node, addExport) {
|
|
192
|
+
switch (node.type) {
|
|
193
|
+
case 'function_declaration':
|
|
194
|
+
if (isExported(node))
|
|
195
|
+
addExport(functionName(node));
|
|
196
|
+
break;
|
|
197
|
+
case 'property_declaration':
|
|
198
|
+
if (isExported(node))
|
|
199
|
+
addExport(this.propertyName(node));
|
|
200
|
+
break;
|
|
201
|
+
case 'class_declaration':
|
|
202
|
+
case 'object_declaration':
|
|
203
|
+
// A private/internal container exposes nothing — skip it and its members.
|
|
204
|
+
if (isExported(node)) {
|
|
205
|
+
addExport(declarationName(node));
|
|
206
|
+
this.extractMembers(node, addExport);
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
extractMembers(container, addExport) {
|
|
212
|
+
const body = childByType(container, 'class_body') ?? childByType(container, 'enum_class_body');
|
|
213
|
+
if (!body)
|
|
214
|
+
return;
|
|
215
|
+
const isInterface = hasTokenChild(container, 'interface');
|
|
216
|
+
body.namedChildren.forEach(member => {
|
|
217
|
+
if (member.type === 'function_declaration') {
|
|
218
|
+
if (isInterface || isExported(member))
|
|
219
|
+
addExport(functionName(member));
|
|
220
|
+
}
|
|
221
|
+
else if (member.type === 'property_declaration') {
|
|
222
|
+
if (isInterface || isExported(member))
|
|
223
|
+
addExport(this.propertyName(member));
|
|
224
|
+
}
|
|
225
|
+
else if (member.type === 'companion_object') {
|
|
226
|
+
// Companion members are reached via the enclosing class name → part of its API.
|
|
227
|
+
member.namedChildren
|
|
228
|
+
.filter(m => m.type === 'class_body')
|
|
229
|
+
.forEach(cb => cb.namedChildren
|
|
230
|
+
.filter(m => m.type === 'function_declaration' && isExported(m))
|
|
231
|
+
.forEach(m => addExport(functionName(m))));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
propertyName(node) {
|
|
236
|
+
// property_declaration → variable_declaration → simple_identifier
|
|
237
|
+
const variable = childByType(node, 'variable_declaration');
|
|
238
|
+
return (variable ? childByType(variable, 'simple_identifier') : null)?.text;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// =============================================================================
|
|
242
|
+
// IMPORT EXTRACTOR
|
|
243
|
+
// =============================================================================
|
|
244
|
+
/**
|
|
245
|
+
* Kotlin/JVM standard-library prefixes that aren't useful as dependency edges.
|
|
246
|
+
* Note: `kotlinx.*` (coroutines, serialization, …) are separate external
|
|
247
|
+
* libraries, not the stdlib, so they are kept as real import edges.
|
|
248
|
+
*/
|
|
249
|
+
function isKotlinStdLib(importPath) {
|
|
250
|
+
return (importPath.startsWith('kotlin.') ||
|
|
251
|
+
importPath.startsWith('java.') ||
|
|
252
|
+
importPath.startsWith('javax.'));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Kotlin import extractor.
|
|
256
|
+
*
|
|
257
|
+
* Handles `import a.b.C`, wildcard `import a.b.*`, and aliased
|
|
258
|
+
* `import a.b.C as D`. The grammar nests `import_header` nodes inside an
|
|
259
|
+
* `import_list`; the engine's import scan descends one level to reach them
|
|
260
|
+
* (see `collectImportNodes` in ast/symbols.ts). Standard-library imports
|
|
261
|
+
* (kotlin.*, kotlinx.*, java.*, javax.*) are filtered out.
|
|
262
|
+
*/
|
|
263
|
+
export class KotlinImportExtractor {
|
|
264
|
+
importNodeTypes = ['import_header'];
|
|
265
|
+
extractImportPath(node) {
|
|
266
|
+
const path = this.getImportPath(node);
|
|
267
|
+
if (!path || isKotlinStdLib(path))
|
|
268
|
+
return null;
|
|
269
|
+
return path;
|
|
270
|
+
}
|
|
271
|
+
processImportSymbols(node) {
|
|
272
|
+
const path = this.getImportPath(node);
|
|
273
|
+
if (!path || isKotlinStdLib(path))
|
|
274
|
+
return null;
|
|
275
|
+
const parts = path.split('.');
|
|
276
|
+
const lastPart = parts[parts.length - 1];
|
|
277
|
+
if (lastPart === '*') {
|
|
278
|
+
const packagePath = parts.slice(0, -1).join('.');
|
|
279
|
+
const packageName = parts[parts.length - 2];
|
|
280
|
+
return { importPath: packagePath, symbols: packageName ? [packageName] : [] };
|
|
281
|
+
}
|
|
282
|
+
// `import a.b.C as D` — the imported symbol is the alias name when present.
|
|
283
|
+
// import_alias wraps the alias as a `type_identifier` (its text is `as D`).
|
|
284
|
+
const alias = childByType(node, 'import_alias');
|
|
285
|
+
const aliasName = alias ? childByType(alias, 'type_identifier')?.text : undefined;
|
|
286
|
+
return { importPath: path, symbols: [aliasName ?? lastPart] };
|
|
287
|
+
}
|
|
288
|
+
getImportPath(node) {
|
|
289
|
+
const identifier = childByType(node, 'identifier');
|
|
290
|
+
if (!identifier)
|
|
291
|
+
return null;
|
|
292
|
+
const hasWildcard = hasTokenChild(node, 'wildcard_import');
|
|
293
|
+
return hasWildcard ? `${identifier.text}.*` : identifier.text;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// =============================================================================
|
|
297
|
+
// SYMBOL EXTRACTOR
|
|
298
|
+
// =============================================================================
|
|
299
|
+
/**
|
|
300
|
+
* Kotlin symbol extractor.
|
|
301
|
+
*
|
|
302
|
+
* Handles `function_declaration`, `class_declaration` (class / interface / enum
|
|
303
|
+
* variants — distinguished by the keyword token), and `object_declaration`.
|
|
304
|
+
* `SymbolInfo.type` only has `function|method|class|interface`, so objects and
|
|
305
|
+
* enums map to `class` (keeping the keyword in the signature).
|
|
306
|
+
*
|
|
307
|
+
* Call sites: `call_expression` — `foo()` (simple_identifier) and `a.b.c()`
|
|
308
|
+
* (the member is the `simple_identifier` inside the trailing `navigation_suffix`).
|
|
309
|
+
*/
|
|
310
|
+
export class KotlinSymbolExtractor {
|
|
311
|
+
symbolNodeTypes = ['function_declaration', 'class_declaration', 'object_declaration'];
|
|
312
|
+
extractSymbol(node, content, parentClass) {
|
|
313
|
+
switch (node.type) {
|
|
314
|
+
case 'function_declaration':
|
|
315
|
+
return this.extractFunctionInfo(node, content, parentClass);
|
|
316
|
+
case 'class_declaration':
|
|
317
|
+
return this.extractClassInfo(node);
|
|
318
|
+
case 'object_declaration':
|
|
319
|
+
return this.extractObjectInfo(node);
|
|
320
|
+
default:
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
extractCallSite(node) {
|
|
325
|
+
if (node.type !== 'call_expression')
|
|
326
|
+
return null;
|
|
327
|
+
const line = node.startPosition.row + 1;
|
|
328
|
+
const callee = node.namedChild(0);
|
|
329
|
+
if (!callee)
|
|
330
|
+
return null;
|
|
331
|
+
let name;
|
|
332
|
+
if (callee.type === 'simple_identifier') {
|
|
333
|
+
name = callee.text; // foo()
|
|
334
|
+
}
|
|
335
|
+
else if (callee.type === 'navigation_expression') {
|
|
336
|
+
// a.b.c() — the called member is the simple_identifier in the last navigation_suffix
|
|
337
|
+
const suffix = callee.namedChildren.filter(c => c.type === 'navigation_suffix').at(-1);
|
|
338
|
+
name = (suffix ? childByType(suffix, 'simple_identifier') : null)?.text;
|
|
339
|
+
}
|
|
340
|
+
if (!name)
|
|
341
|
+
return null;
|
|
342
|
+
return { symbol: name, line, key: `${name}:${line}` };
|
|
343
|
+
}
|
|
344
|
+
extractFunctionInfo(node, content, parentClass) {
|
|
345
|
+
const name = functionName(node);
|
|
346
|
+
if (!name)
|
|
347
|
+
return null;
|
|
348
|
+
return {
|
|
349
|
+
name,
|
|
350
|
+
type: parentClass ? 'method' : 'function',
|
|
351
|
+
startLine: node.startPosition.row + 1,
|
|
352
|
+
endLine: node.endPosition.row + 1,
|
|
353
|
+
parentClass,
|
|
354
|
+
signature: functionSignature(node, content),
|
|
355
|
+
parameters: functionParameters(node),
|
|
356
|
+
returnType: functionReturnType(node),
|
|
357
|
+
complexity: calculateComplexity(node),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
extractClassInfo(node) {
|
|
361
|
+
const name = declarationName(node);
|
|
362
|
+
if (!name)
|
|
363
|
+
return null;
|
|
364
|
+
if (hasTokenChild(node, 'interface')) {
|
|
365
|
+
return this.makeSymbol(node, name, 'interface', `interface ${name}`);
|
|
366
|
+
}
|
|
367
|
+
if (hasTokenChild(node, 'enum')) {
|
|
368
|
+
return this.makeSymbol(node, name, 'class', `enum class ${name}`);
|
|
369
|
+
}
|
|
370
|
+
return this.makeSymbol(node, name, 'class', `class ${name}`);
|
|
371
|
+
}
|
|
372
|
+
extractObjectInfo(node) {
|
|
373
|
+
const name = declarationName(node);
|
|
374
|
+
if (!name)
|
|
375
|
+
return null;
|
|
376
|
+
return this.makeSymbol(node, name, 'class', `object ${name}`);
|
|
377
|
+
}
|
|
378
|
+
makeSymbol(node, name, type, signature) {
|
|
379
|
+
return {
|
|
380
|
+
name,
|
|
381
|
+
type,
|
|
382
|
+
startLine: node.startPosition.row + 1,
|
|
383
|
+
endLine: node.endPosition.row + 1,
|
|
384
|
+
signature,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// =============================================================================
|
|
389
|
+
// LANGUAGE DEFINITION
|
|
390
|
+
// =============================================================================
|
|
391
|
+
export const kotlinDefinition = {
|
|
392
|
+
id: 'kotlin',
|
|
393
|
+
extensions: ['kt'],
|
|
394
|
+
grammar: Kotlin,
|
|
395
|
+
traverser: new KotlinTraverser(),
|
|
396
|
+
exportExtractor: new KotlinExportExtractor(),
|
|
397
|
+
importExtractor: new KotlinImportExtractor(),
|
|
398
|
+
symbolExtractor: new KotlinSymbolExtractor(),
|
|
399
|
+
complexity: {
|
|
400
|
+
decisionPoints: [
|
|
401
|
+
'if_expression',
|
|
402
|
+
'when_entry',
|
|
403
|
+
'for_statement',
|
|
404
|
+
'while_statement',
|
|
405
|
+
'do_while_statement',
|
|
406
|
+
'catch_block',
|
|
407
|
+
'elvis_expression',
|
|
408
|
+
'conjunction_expression', // &&
|
|
409
|
+
'disjunction_expression', // ||
|
|
410
|
+
],
|
|
411
|
+
nestingTypes: [
|
|
412
|
+
'if_expression',
|
|
413
|
+
'when_expression',
|
|
414
|
+
'for_statement',
|
|
415
|
+
'while_statement',
|
|
416
|
+
'do_while_statement',
|
|
417
|
+
'catch_block',
|
|
418
|
+
'lambda_literal',
|
|
419
|
+
'anonymous_function',
|
|
420
|
+
],
|
|
421
|
+
nonNestingTypes: ['when_entry', 'elvis_expression'],
|
|
422
|
+
lambdaTypes: ['lambda_literal', 'anonymous_function'],
|
|
423
|
+
operatorSymbols: new Set([
|
|
424
|
+
'+',
|
|
425
|
+
'-',
|
|
426
|
+
'*',
|
|
427
|
+
'/',
|
|
428
|
+
'%',
|
|
429
|
+
'==',
|
|
430
|
+
'!=',
|
|
431
|
+
'===',
|
|
432
|
+
'!==',
|
|
433
|
+
'<',
|
|
434
|
+
'>',
|
|
435
|
+
'<=',
|
|
436
|
+
'>=',
|
|
437
|
+
'=',
|
|
438
|
+
'+=',
|
|
439
|
+
'-=',
|
|
440
|
+
'*=',
|
|
441
|
+
'/=',
|
|
442
|
+
'%=',
|
|
443
|
+
'&&',
|
|
444
|
+
'||',
|
|
445
|
+
'!',
|
|
446
|
+
'?:',
|
|
447
|
+
'?.',
|
|
448
|
+
'.',
|
|
449
|
+
'::',
|
|
450
|
+
'->',
|
|
451
|
+
'..',
|
|
452
|
+
'(',
|
|
453
|
+
')',
|
|
454
|
+
'[',
|
|
455
|
+
']',
|
|
456
|
+
'{',
|
|
457
|
+
'}',
|
|
458
|
+
]),
|
|
459
|
+
operatorKeywords: new Set([
|
|
460
|
+
'if',
|
|
461
|
+
'else',
|
|
462
|
+
'when',
|
|
463
|
+
'for',
|
|
464
|
+
'while',
|
|
465
|
+
'do',
|
|
466
|
+
'return',
|
|
467
|
+
'break',
|
|
468
|
+
'continue',
|
|
469
|
+
'throw',
|
|
470
|
+
'try',
|
|
471
|
+
'catch',
|
|
472
|
+
'finally',
|
|
473
|
+
'fun',
|
|
474
|
+
'class',
|
|
475
|
+
'object',
|
|
476
|
+
'interface',
|
|
477
|
+
'enum',
|
|
478
|
+
'val',
|
|
479
|
+
'var',
|
|
480
|
+
'is',
|
|
481
|
+
'as',
|
|
482
|
+
'in',
|
|
483
|
+
'by',
|
|
484
|
+
'import',
|
|
485
|
+
'package',
|
|
486
|
+
'override',
|
|
487
|
+
'abstract',
|
|
488
|
+
'open',
|
|
489
|
+
'sealed',
|
|
490
|
+
'data',
|
|
491
|
+
'suspend',
|
|
492
|
+
'companion',
|
|
493
|
+
'private',
|
|
494
|
+
'internal',
|
|
495
|
+
'protected',
|
|
496
|
+
'public',
|
|
497
|
+
'this',
|
|
498
|
+
'super',
|
|
499
|
+
'null',
|
|
500
|
+
]),
|
|
501
|
+
},
|
|
502
|
+
symbols: {
|
|
503
|
+
callExpressionTypes: ['call_expression'],
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
//# sourceMappingURL=kotlin.js.map
|