driftdetect-core 0.1.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/dist/analyzers/ast-analyzer.d.ts +251 -0
- package/dist/analyzers/ast-analyzer.d.ts.map +1 -0
- package/dist/analyzers/ast-analyzer.js +548 -0
- package/dist/analyzers/ast-analyzer.js.map +1 -0
- package/dist/analyzers/flow-analyzer.d.ts +241 -0
- package/dist/analyzers/flow-analyzer.d.ts.map +1 -0
- package/dist/analyzers/flow-analyzer.js +1219 -0
- package/dist/analyzers/flow-analyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +18 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +19 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/semantic-analyzer.d.ts +252 -0
- package/dist/analyzers/semantic-analyzer.d.ts.map +1 -0
- package/dist/analyzers/semantic-analyzer.js +1182 -0
- package/dist/analyzers/semantic-analyzer.js.map +1 -0
- package/dist/analyzers/type-analyzer.d.ts +289 -0
- package/dist/analyzers/type-analyzer.d.ts.map +1 -0
- package/dist/analyzers/type-analyzer.js +1269 -0
- package/dist/analyzers/type-analyzer.js.map +1 -0
- package/dist/analyzers/types.d.ts +537 -0
- package/dist/analyzers/types.d.ts.map +1 -0
- package/dist/analyzers/types.js +11 -0
- package/dist/analyzers/types.js.map +1 -0
- package/dist/config/config-loader.d.ts +166 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +429 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/config-validator.d.ts +204 -0
- package/dist/config/config-validator.d.ts.map +1 -0
- package/dist/config/config-validator.js +632 -0
- package/dist/config/config-validator.js.map +1 -0
- package/dist/config/defaults.d.ts +8 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +26 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +10 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +47 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +7 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/exporter.d.ts +21 -0
- package/dist/manifest/exporter.d.ts.map +1 -0
- package/dist/manifest/exporter.js +339 -0
- package/dist/manifest/exporter.js.map +1 -0
- package/dist/manifest/index.d.ts +14 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +15 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/manifest-store.d.ts +111 -0
- package/dist/manifest/manifest-store.d.ts.map +1 -0
- package/dist/manifest/manifest-store.js +418 -0
- package/dist/manifest/manifest-store.js.map +1 -0
- package/dist/manifest/types.d.ts +238 -0
- package/dist/manifest/types.d.ts.map +1 -0
- package/dist/manifest/types.js +11 -0
- package/dist/manifest/types.js.map +1 -0
- package/dist/matcher/confidence-scorer.d.ts +188 -0
- package/dist/matcher/confidence-scorer.d.ts.map +1 -0
- package/dist/matcher/confidence-scorer.js +302 -0
- package/dist/matcher/confidence-scorer.js.map +1 -0
- package/dist/matcher/index.d.ts +24 -0
- package/dist/matcher/index.d.ts.map +1 -0
- package/dist/matcher/index.js +26 -0
- package/dist/matcher/index.js.map +1 -0
- package/dist/matcher/outlier-detector.d.ts +252 -0
- package/dist/matcher/outlier-detector.d.ts.map +1 -0
- package/dist/matcher/outlier-detector.js +544 -0
- package/dist/matcher/outlier-detector.js.map +1 -0
- package/dist/matcher/pattern-matcher.d.ts +169 -0
- package/dist/matcher/pattern-matcher.d.ts.map +1 -0
- package/dist/matcher/pattern-matcher.js +692 -0
- package/dist/matcher/pattern-matcher.js.map +1 -0
- package/dist/matcher/types.d.ts +476 -0
- package/dist/matcher/types.d.ts.map +1 -0
- package/dist/matcher/types.js +36 -0
- package/dist/matcher/types.js.map +1 -0
- package/dist/parsers/base-parser.d.ts +282 -0
- package/dist/parsers/base-parser.d.ts.map +1 -0
- package/dist/parsers/base-parser.js +421 -0
- package/dist/parsers/base-parser.js.map +1 -0
- package/dist/parsers/css-parser.d.ts +225 -0
- package/dist/parsers/css-parser.d.ts.map +1 -0
- package/dist/parsers/css-parser.js +477 -0
- package/dist/parsers/css-parser.js.map +1 -0
- package/dist/parsers/index.d.ts +15 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +15 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/json-parser.d.ts +219 -0
- package/dist/parsers/json-parser.d.ts.map +1 -0
- package/dist/parsers/json-parser.js +602 -0
- package/dist/parsers/json-parser.js.map +1 -0
- package/dist/parsers/markdown-parser.d.ts +276 -0
- package/dist/parsers/markdown-parser.d.ts.map +1 -0
- package/dist/parsers/markdown-parser.js +731 -0
- package/dist/parsers/markdown-parser.js.map +1 -0
- package/dist/parsers/parser-manager.d.ts +294 -0
- package/dist/parsers/parser-manager.d.ts.map +1 -0
- package/dist/parsers/parser-manager.js +738 -0
- package/dist/parsers/parser-manager.js.map +1 -0
- package/dist/parsers/python-parser.d.ts +204 -0
- package/dist/parsers/python-parser.d.ts.map +1 -0
- package/dist/parsers/python-parser.js +517 -0
- package/dist/parsers/python-parser.js.map +1 -0
- package/dist/parsers/types.d.ts +43 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +7 -0
- package/dist/parsers/types.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +264 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +658 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/rules/evaluator.d.ts +305 -0
- package/dist/rules/evaluator.d.ts.map +1 -0
- package/dist/rules/evaluator.js +579 -0
- package/dist/rules/evaluator.js.map +1 -0
- package/dist/rules/index.d.ts +13 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +13 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/quick-fix-generator.d.ts +334 -0
- package/dist/rules/quick-fix-generator.d.ts.map +1 -0
- package/dist/rules/quick-fix-generator.js +1075 -0
- package/dist/rules/quick-fix-generator.js.map +1 -0
- package/dist/rules/rule-engine.d.ts +241 -0
- package/dist/rules/rule-engine.d.ts.map +1 -0
- package/dist/rules/rule-engine.js +585 -0
- package/dist/rules/rule-engine.js.map +1 -0
- package/dist/rules/severity-manager.d.ts +394 -0
- package/dist/rules/severity-manager.d.ts.map +1 -0
- package/dist/rules/severity-manager.js +619 -0
- package/dist/rules/severity-manager.js.map +1 -0
- package/dist/rules/types.d.ts +370 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +133 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/variant-manager.d.ts +388 -0
- package/dist/rules/variant-manager.d.ts.map +1 -0
- package/dist/rules/variant-manager.js +777 -0
- package/dist/rules/variant-manager.js.map +1 -0
- package/dist/scanner/change-detector.d.ts +164 -0
- package/dist/scanner/change-detector.d.ts.map +1 -0
- package/dist/scanner/change-detector.js +263 -0
- package/dist/scanner/change-detector.js.map +1 -0
- package/dist/scanner/dependency-graph.d.ts +270 -0
- package/dist/scanner/dependency-graph.d.ts.map +1 -0
- package/dist/scanner/dependency-graph.js +436 -0
- package/dist/scanner/dependency-graph.js.map +1 -0
- package/dist/scanner/file-walker.d.ts +127 -0
- package/dist/scanner/file-walker.d.ts.map +1 -0
- package/dist/scanner/file-walker.js +526 -0
- package/dist/scanner/file-walker.js.map +1 -0
- package/dist/scanner/index.d.ts +12 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +12 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/types.d.ts +218 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +10 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/scanner/worker-pool.d.ts +317 -0
- package/dist/scanner/worker-pool.d.ts.map +1 -0
- package/dist/scanner/worker-pool.js +571 -0
- package/dist/scanner/worker-pool.js.map +1 -0
- package/dist/store/cache-manager.d.ts +179 -0
- package/dist/store/cache-manager.d.ts.map +1 -0
- package/dist/store/cache-manager.js +391 -0
- package/dist/store/cache-manager.js.map +1 -0
- package/dist/store/history-store.d.ts +314 -0
- package/dist/store/history-store.d.ts.map +1 -0
- package/dist/store/history-store.js +707 -0
- package/dist/store/history-store.js.map +1 -0
- package/dist/store/index.d.ts +20 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +26 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/lock-file-manager.d.ts +202 -0
- package/dist/store/lock-file-manager.d.ts.map +1 -0
- package/dist/store/lock-file-manager.js +475 -0
- package/dist/store/lock-file-manager.js.map +1 -0
- package/dist/store/pattern-store.d.ts +289 -0
- package/dist/store/pattern-store.d.ts.map +1 -0
- package/dist/store/pattern-store.js +936 -0
- package/dist/store/pattern-store.js.map +1 -0
- package/dist/store/schema-validator.d.ts +159 -0
- package/dist/store/schema-validator.d.ts.map +1 -0
- package/dist/store/schema-validator.js +1096 -0
- package/dist/store/schema-validator.js.map +1 -0
- package/dist/store/types.d.ts +585 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +82 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types/analysis.d.ts +19 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +5 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/common.d.ts +7 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +5 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/patterns.d.ts +40 -0
- package/dist/types/patterns.d.ts.map +1 -0
- package/dist/types/patterns.js +7 -0
- package/dist/types/patterns.js.map +1 -0
- package/dist/types/violations.d.ts +7 -0
- package/dist/types/violations.d.ts.map +1 -0
- package/dist/types/violations.js +7 -0
- package/dist/types/violations.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Analyzer - TypeScript type information extraction and analysis
|
|
3
|
+
*
|
|
4
|
+
* Provides type extraction from AST nodes, type relationship analysis,
|
|
5
|
+
* type compatibility checking, and type coverage calculation.
|
|
6
|
+
*
|
|
7
|
+
* @requirements 3.5 - Parser SHALL provide a unified AST query interface across all languages
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Primitive type names
|
|
11
|
+
*/
|
|
12
|
+
const PRIMITIVE_TYPES = new Set([
|
|
13
|
+
'string',
|
|
14
|
+
'number',
|
|
15
|
+
'boolean',
|
|
16
|
+
'bigint',
|
|
17
|
+
'symbol',
|
|
18
|
+
'null',
|
|
19
|
+
'undefined',
|
|
20
|
+
'void',
|
|
21
|
+
'never',
|
|
22
|
+
'any',
|
|
23
|
+
'unknown',
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* TypeScript utility types
|
|
27
|
+
*/
|
|
28
|
+
const UTILITY_TYPES = new Set([
|
|
29
|
+
'Partial',
|
|
30
|
+
'Required',
|
|
31
|
+
'Readonly',
|
|
32
|
+
'Record',
|
|
33
|
+
'Pick',
|
|
34
|
+
'Omit',
|
|
35
|
+
'Exclude',
|
|
36
|
+
'Extract',
|
|
37
|
+
'NonNullable',
|
|
38
|
+
'Parameters',
|
|
39
|
+
'ConstructorParameters',
|
|
40
|
+
'ReturnType',
|
|
41
|
+
'InstanceType',
|
|
42
|
+
'ThisParameterType',
|
|
43
|
+
'OmitThisParameter',
|
|
44
|
+
'ThisType',
|
|
45
|
+
'Awaited',
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Type Analyzer class for TypeScript type information extraction and analysis.
|
|
49
|
+
*
|
|
50
|
+
* Provides a unified interface for analyzing type information across
|
|
51
|
+
* TypeScript and JavaScript code.
|
|
52
|
+
*
|
|
53
|
+
* @requirements 3.5 - Unified AST query interface across all languages
|
|
54
|
+
*/
|
|
55
|
+
export class TypeAnalyzer {
|
|
56
|
+
/** Cache for resolved types */
|
|
57
|
+
typeCache = new Map();
|
|
58
|
+
/** Type definitions found during analysis */
|
|
59
|
+
typeDefinitions = new Map();
|
|
60
|
+
/**
|
|
61
|
+
* Extract type information from an AST node.
|
|
62
|
+
*
|
|
63
|
+
* @param node - The AST node to extract type from
|
|
64
|
+
* @param options - Extraction options
|
|
65
|
+
* @returns Type information or null if no type found
|
|
66
|
+
*/
|
|
67
|
+
extractType(node, options = {}) {
|
|
68
|
+
const { maxDepth = 10 } = options;
|
|
69
|
+
return this.extractTypeFromNode(node, 0, maxDepth);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Analyze all types in an AST.
|
|
73
|
+
*
|
|
74
|
+
* @param ast - The AST to analyze
|
|
75
|
+
* @param options - Analysis options
|
|
76
|
+
* @returns Type analysis result
|
|
77
|
+
*/
|
|
78
|
+
analyzeTypes(ast, options = {}) {
|
|
79
|
+
const types = new Map();
|
|
80
|
+
const errors = [];
|
|
81
|
+
// Clear caches for fresh analysis
|
|
82
|
+
this.typeCache.clear();
|
|
83
|
+
this.typeDefinitions.clear();
|
|
84
|
+
// First pass: collect type definitions
|
|
85
|
+
this.collectTypeDefinitions(ast.rootNode);
|
|
86
|
+
// Second pass: analyze all nodes
|
|
87
|
+
this.traverseForTypes(ast.rootNode, types, errors, options);
|
|
88
|
+
// Calculate coverage if requested
|
|
89
|
+
const coverage = options.calculateCoverage
|
|
90
|
+
? this.calculateCoverageFromTypes(types)
|
|
91
|
+
: 0;
|
|
92
|
+
return { types, errors, coverage };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if type1 is a subtype of type2.
|
|
96
|
+
*
|
|
97
|
+
* @param type1 - The potential subtype
|
|
98
|
+
* @param type2 - The potential supertype
|
|
99
|
+
* @returns true if type1 is a subtype of type2
|
|
100
|
+
*/
|
|
101
|
+
isSubtypeOf(type1, type2) {
|
|
102
|
+
// Same type is always a subtype of itself
|
|
103
|
+
if (this.areTypesEquivalent(type1, type2)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
// 'never' is a subtype of everything
|
|
107
|
+
if (type1.kind === 'never') {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
// Everything is a subtype of 'unknown'
|
|
111
|
+
if (type2.kind === 'unknown') {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
// Everything is a subtype of 'any' (and 'any' is a subtype of everything)
|
|
115
|
+
if (type2.kind === 'any' || type1.kind === 'any') {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// null and undefined are subtypes of nullable types
|
|
119
|
+
if ((type1.kind === 'null' || type1.kind === 'undefined') && type2.isNullable) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// Union type: type1 is subtype if it's a subtype of any union member
|
|
123
|
+
if (type2.kind === 'union' && type2.unionTypes) {
|
|
124
|
+
return type2.unionTypes.some((t) => this.isSubtypeOf(type1, t));
|
|
125
|
+
}
|
|
126
|
+
// type1 union: all members must be subtypes of type2
|
|
127
|
+
if (type1.kind === 'union' && type1.unionTypes) {
|
|
128
|
+
return type1.unionTypes.every((t) => this.isSubtypeOf(t, type2));
|
|
129
|
+
}
|
|
130
|
+
// Intersection type as supertype: type1 is subtype if it's a subtype of all intersection members
|
|
131
|
+
if (type2.kind === 'intersection' && type2.intersectionTypes) {
|
|
132
|
+
return type2.intersectionTypes.every((t) => this.isSubtypeOf(type1, t));
|
|
133
|
+
}
|
|
134
|
+
// Intersection type as subtype: A & B is a subtype of A and a subtype of B
|
|
135
|
+
if (type1.kind === 'intersection' && type1.intersectionTypes) {
|
|
136
|
+
return type1.intersectionTypes.some((t) => this.isSubtypeOf(t, type2));
|
|
137
|
+
}
|
|
138
|
+
// Array subtyping (covariant)
|
|
139
|
+
if (type1.kind === 'array' && type2.kind === 'array') {
|
|
140
|
+
if (type1.elementType && type2.elementType) {
|
|
141
|
+
return this.isSubtypeOf(type1.elementType, type2.elementType);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Object subtyping (structural)
|
|
145
|
+
if (type1.kind === 'object' && type2.kind === 'object') {
|
|
146
|
+
return this.isObjectSubtype(type1, type2);
|
|
147
|
+
}
|
|
148
|
+
// Function subtyping (contravariant in parameters, covariant in return)
|
|
149
|
+
if (type1.kind === 'function' && type2.kind === 'function') {
|
|
150
|
+
return this.isFunctionSubtype(type1, type2);
|
|
151
|
+
}
|
|
152
|
+
// Class/interface inheritance
|
|
153
|
+
if ((type1.kind === 'class' || type1.kind === 'interface') &&
|
|
154
|
+
(type2.kind === 'class' || type2.kind === 'interface')) {
|
|
155
|
+
// Check by name for now (would need full type resolution for accurate check)
|
|
156
|
+
if (type1.name && type2.name && type1.name === type2.name) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if two types are compatible (assignable).
|
|
164
|
+
*
|
|
165
|
+
* @param type1 - The source type (being assigned)
|
|
166
|
+
* @param type2 - The target type (being assigned to)
|
|
167
|
+
* @returns true if type1 is compatible with type2
|
|
168
|
+
*/
|
|
169
|
+
areTypesCompatible(type1, type2) {
|
|
170
|
+
// Subtype relationship implies compatibility
|
|
171
|
+
if (this.isSubtypeOf(type1, type2)) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
// 'any' is compatible with everything
|
|
175
|
+
if (type1.kind === 'any' || type2.kind === 'any') {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
// Check for literal type compatibility with primitive
|
|
179
|
+
if (type1.kind === 'literal' && type2.kind === 'primitive') {
|
|
180
|
+
const literalPrimitive = this.getLiteralPrimitiveType(type1);
|
|
181
|
+
return literalPrimitive === type2.name;
|
|
182
|
+
}
|
|
183
|
+
// Optional types are compatible with undefined
|
|
184
|
+
if (type2.isOptional && (type1.kind === 'undefined' || type1.kind === 'void')) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
// Nullable types are compatible with null
|
|
188
|
+
if (type2.isNullable && type1.kind === 'null') {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get type coverage information for an AST.
|
|
195
|
+
*
|
|
196
|
+
* @param ast - The AST to analyze
|
|
197
|
+
* @returns Type coverage information
|
|
198
|
+
*/
|
|
199
|
+
getTypeCoverage(ast) {
|
|
200
|
+
const locations = this.collectTypeableLocations(ast.rootNode);
|
|
201
|
+
const missingTypeLocations = [];
|
|
202
|
+
let typedLocations = 0;
|
|
203
|
+
let inferredLocations = 0;
|
|
204
|
+
let anyLocations = 0;
|
|
205
|
+
let untypedLocations = 0;
|
|
206
|
+
for (const loc of locations) {
|
|
207
|
+
const typeInfo = this.extractType(loc.node);
|
|
208
|
+
if (!typeInfo) {
|
|
209
|
+
untypedLocations++;
|
|
210
|
+
missingTypeLocations.push(loc.location);
|
|
211
|
+
}
|
|
212
|
+
else if (typeInfo.kind === 'any') {
|
|
213
|
+
anyLocations++;
|
|
214
|
+
}
|
|
215
|
+
else if (loc.isInferred) {
|
|
216
|
+
inferredLocations++;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
typedLocations++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const totalLocations = locations.length;
|
|
223
|
+
const coverage = totalLocations > 0
|
|
224
|
+
? ((typedLocations + inferredLocations) / totalLocations) * 100
|
|
225
|
+
: 100;
|
|
226
|
+
return {
|
|
227
|
+
totalLocations,
|
|
228
|
+
typedLocations,
|
|
229
|
+
inferredLocations,
|
|
230
|
+
anyLocations,
|
|
231
|
+
untypedLocations,
|
|
232
|
+
coverage,
|
|
233
|
+
missingTypeLocations,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Analyze type relationships in an AST.
|
|
238
|
+
*
|
|
239
|
+
* @param ast - The AST to analyze
|
|
240
|
+
* @returns Array of type relationships found
|
|
241
|
+
*/
|
|
242
|
+
analyzeTypeRelationships(ast) {
|
|
243
|
+
const relationships = [];
|
|
244
|
+
// Collect all type definitions first
|
|
245
|
+
this.collectTypeDefinitions(ast.rootNode);
|
|
246
|
+
// Find extends/implements relationships
|
|
247
|
+
this.traverseNode(ast.rootNode, (node) => {
|
|
248
|
+
// Class declarations with extends
|
|
249
|
+
if (node.type === 'class_declaration' || node.type === 'ClassDeclaration') {
|
|
250
|
+
const extendsClause = this.findChildByType(node, 'extends_clause') ||
|
|
251
|
+
this.findChildByType(node, 'heritage_clause');
|
|
252
|
+
if (extendsClause) {
|
|
253
|
+
const className = this.getClassName(node);
|
|
254
|
+
const baseClassName = this.getExtendsName(extendsClause);
|
|
255
|
+
if (className && baseClassName) {
|
|
256
|
+
const classType = this.createNamedType(className, 'class');
|
|
257
|
+
const baseType = this.createNamedType(baseClassName, 'class');
|
|
258
|
+
relationships.push({
|
|
259
|
+
sourceType: classType,
|
|
260
|
+
targetType: baseType,
|
|
261
|
+
kind: 'extends',
|
|
262
|
+
confidence: 1.0,
|
|
263
|
+
});
|
|
264
|
+
relationships.push({
|
|
265
|
+
sourceType: classType,
|
|
266
|
+
targetType: baseType,
|
|
267
|
+
kind: 'subtype',
|
|
268
|
+
confidence: 1.0,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Implements clause
|
|
273
|
+
const implementsClause = this.findChildByType(node, 'implements_clause');
|
|
274
|
+
if (implementsClause) {
|
|
275
|
+
const className = this.getClassName(node);
|
|
276
|
+
const interfaceNames = this.getImplementsNames(implementsClause);
|
|
277
|
+
if (className) {
|
|
278
|
+
const classType = this.createNamedType(className, 'class');
|
|
279
|
+
for (const interfaceName of interfaceNames) {
|
|
280
|
+
const interfaceType = this.createNamedType(interfaceName, 'interface');
|
|
281
|
+
relationships.push({
|
|
282
|
+
sourceType: classType,
|
|
283
|
+
targetType: interfaceType,
|
|
284
|
+
kind: 'implements',
|
|
285
|
+
confidence: 1.0,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Interface declarations with extends
|
|
292
|
+
if (node.type === 'interface_declaration' || node.type === 'TSInterfaceDeclaration') {
|
|
293
|
+
const extendsClause = this.findChildByType(node, 'extends_clause') ||
|
|
294
|
+
this.findChildByType(node, 'extends_type_clause');
|
|
295
|
+
if (extendsClause) {
|
|
296
|
+
const interfaceName = this.getInterfaceName(node);
|
|
297
|
+
const baseNames = this.getExtendsNames(extendsClause);
|
|
298
|
+
if (interfaceName) {
|
|
299
|
+
const interfaceType = this.createNamedType(interfaceName, 'interface');
|
|
300
|
+
for (const baseName of baseNames) {
|
|
301
|
+
const baseType = this.createNamedType(baseName, 'interface');
|
|
302
|
+
relationships.push({
|
|
303
|
+
sourceType: interfaceType,
|
|
304
|
+
targetType: baseType,
|
|
305
|
+
kind: 'extends',
|
|
306
|
+
confidence: 1.0,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return relationships;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Check if two types are equivalent.
|
|
317
|
+
*
|
|
318
|
+
* @param type1 - First type
|
|
319
|
+
* @param type2 - Second type
|
|
320
|
+
* @returns true if types are equivalent
|
|
321
|
+
*/
|
|
322
|
+
areTypesEquivalent(type1, type2) {
|
|
323
|
+
// Same kind check
|
|
324
|
+
if (type1.kind !== type2.kind) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
// Same name for named types
|
|
328
|
+
if (type1.name !== type2.name) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
// Same text representation
|
|
332
|
+
if (type1.text !== type2.text) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
// Check nullable/optional flags
|
|
336
|
+
if (type1.isNullable !== type2.isNullable || type1.isOptional !== type2.isOptional) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
// Check union types
|
|
340
|
+
if (type1.kind === 'union') {
|
|
341
|
+
if (!type1.unionTypes || !type2.unionTypes) {
|
|
342
|
+
return type1.unionTypes === type2.unionTypes;
|
|
343
|
+
}
|
|
344
|
+
if (type1.unionTypes.length !== type2.unionTypes.length) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
return type1.unionTypes.every((t1, i) => this.areTypesEquivalent(t1, type2.unionTypes[i]));
|
|
348
|
+
}
|
|
349
|
+
// Check intersection types
|
|
350
|
+
if (type1.kind === 'intersection') {
|
|
351
|
+
if (!type1.intersectionTypes || !type2.intersectionTypes) {
|
|
352
|
+
return type1.intersectionTypes === type2.intersectionTypes;
|
|
353
|
+
}
|
|
354
|
+
if (type1.intersectionTypes.length !== type2.intersectionTypes.length) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
return type1.intersectionTypes.every((t1, i) => this.areTypesEquivalent(t1, type2.intersectionTypes[i]));
|
|
358
|
+
}
|
|
359
|
+
// Check array element types
|
|
360
|
+
if (type1.kind === 'array') {
|
|
361
|
+
if (!type1.elementType || !type2.elementType) {
|
|
362
|
+
return type1.elementType === type2.elementType;
|
|
363
|
+
}
|
|
364
|
+
return this.areTypesEquivalent(type1.elementType, type2.elementType);
|
|
365
|
+
}
|
|
366
|
+
// Check function types
|
|
367
|
+
if (type1.kind === 'function') {
|
|
368
|
+
return this.areFunctionTypesEquivalent(type1, type2);
|
|
369
|
+
}
|
|
370
|
+
// Check object types
|
|
371
|
+
if (type1.kind === 'object') {
|
|
372
|
+
return this.areObjectTypesEquivalent(type1, type2);
|
|
373
|
+
}
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
// ============================================
|
|
377
|
+
// Private Helper Methods
|
|
378
|
+
// ============================================
|
|
379
|
+
/**
|
|
380
|
+
* Extract type from an AST node recursively.
|
|
381
|
+
*/
|
|
382
|
+
extractTypeFromNode(node, depth, maxDepth) {
|
|
383
|
+
if (depth > maxDepth) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
// Check cache first - include text in key to differentiate nodes at same position
|
|
387
|
+
const cacheKey = `${node.startPosition.row}:${node.startPosition.column}:${node.type}:${node.text}`;
|
|
388
|
+
if (this.typeCache.has(cacheKey)) {
|
|
389
|
+
return this.typeCache.get(cacheKey);
|
|
390
|
+
}
|
|
391
|
+
const typeInfo = this.extractTypeFromNodeInternal(node, depth, maxDepth);
|
|
392
|
+
if (typeInfo) {
|
|
393
|
+
this.typeCache.set(cacheKey, typeInfo);
|
|
394
|
+
}
|
|
395
|
+
return typeInfo;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Internal type extraction logic.
|
|
399
|
+
*/
|
|
400
|
+
extractTypeFromNodeInternal(node, depth, maxDepth) {
|
|
401
|
+
const nodeType = node.type;
|
|
402
|
+
// Type annotation nodes
|
|
403
|
+
if (nodeType === 'type_annotation' || nodeType === 'TSTypeAnnotation') {
|
|
404
|
+
const typeNode = node.children[0];
|
|
405
|
+
if (typeNode) {
|
|
406
|
+
return this.extractTypeFromNode(typeNode, depth + 1, maxDepth);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// Primitive types
|
|
410
|
+
if (nodeType === 'predefined_type' || nodeType === 'TSStringKeyword' ||
|
|
411
|
+
nodeType === 'TSNumberKeyword' || nodeType === 'TSBooleanKeyword' ||
|
|
412
|
+
nodeType === 'TSAnyKeyword' || nodeType === 'TSVoidKeyword' ||
|
|
413
|
+
nodeType === 'TSNeverKeyword' || nodeType === 'TSUnknownKeyword' ||
|
|
414
|
+
nodeType === 'TSNullKeyword' || nodeType === 'TSUndefinedKeyword') {
|
|
415
|
+
return this.createPrimitiveType(node.text);
|
|
416
|
+
}
|
|
417
|
+
// Type reference (named types)
|
|
418
|
+
if (nodeType === 'type_identifier' || nodeType === 'TSTypeReference' ||
|
|
419
|
+
nodeType === 'generic_type') {
|
|
420
|
+
return this.extractTypeReference(node, depth, maxDepth);
|
|
421
|
+
}
|
|
422
|
+
// Union types
|
|
423
|
+
if (nodeType === 'union_type' || nodeType === 'TSUnionType') {
|
|
424
|
+
return this.extractUnionType(node, depth, maxDepth);
|
|
425
|
+
}
|
|
426
|
+
// Intersection types
|
|
427
|
+
if (nodeType === 'intersection_type' || nodeType === 'TSIntersectionType') {
|
|
428
|
+
return this.extractIntersectionType(node, depth, maxDepth);
|
|
429
|
+
}
|
|
430
|
+
// Array types
|
|
431
|
+
if (nodeType === 'array_type' || nodeType === 'TSArrayType') {
|
|
432
|
+
return this.extractArrayType(node, depth, maxDepth);
|
|
433
|
+
}
|
|
434
|
+
// Tuple types
|
|
435
|
+
if (nodeType === 'tuple_type' || nodeType === 'TSTupleType') {
|
|
436
|
+
return this.extractTupleType(node, depth, maxDepth);
|
|
437
|
+
}
|
|
438
|
+
// Function types
|
|
439
|
+
if (nodeType === 'function_type' || nodeType === 'TSFunctionType') {
|
|
440
|
+
return this.extractFunctionType(node, depth, maxDepth);
|
|
441
|
+
}
|
|
442
|
+
// Object/interface types
|
|
443
|
+
if (nodeType === 'object_type' || nodeType === 'TSTypeLiteral') {
|
|
444
|
+
return this.extractObjectType(node, depth, maxDepth);
|
|
445
|
+
}
|
|
446
|
+
// Literal types
|
|
447
|
+
if (nodeType === 'literal_type' || nodeType === 'TSLiteralType') {
|
|
448
|
+
return this.extractLiteralType(node);
|
|
449
|
+
}
|
|
450
|
+
// Conditional types
|
|
451
|
+
if (nodeType === 'conditional_type' || nodeType === 'TSConditionalType') {
|
|
452
|
+
return this.createTypeInfo('conditional', node.text);
|
|
453
|
+
}
|
|
454
|
+
// Mapped types
|
|
455
|
+
if (nodeType === 'mapped_type' || nodeType === 'TSMappedType') {
|
|
456
|
+
return this.createTypeInfo('mapped', node.text);
|
|
457
|
+
}
|
|
458
|
+
// Indexed access types
|
|
459
|
+
if (nodeType === 'indexed_access_type' || nodeType === 'TSIndexedAccessType') {
|
|
460
|
+
return this.createTypeInfo('indexed', node.text);
|
|
461
|
+
}
|
|
462
|
+
// Type parameters
|
|
463
|
+
if (nodeType === 'type_parameter' || nodeType === 'TSTypeParameter') {
|
|
464
|
+
return this.extractTypeParameter(node);
|
|
465
|
+
}
|
|
466
|
+
// Variable declarations with type annotations
|
|
467
|
+
if (nodeType === 'variable_declarator' || nodeType === 'VariableDeclarator') {
|
|
468
|
+
const typeAnnotation = this.findChildByType(node, 'type_annotation') ||
|
|
469
|
+
this.findChildByType(node, 'TSTypeAnnotation');
|
|
470
|
+
if (typeAnnotation) {
|
|
471
|
+
return this.extractTypeFromNode(typeAnnotation, depth + 1, maxDepth);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Function declarations
|
|
475
|
+
if (nodeType === 'function_declaration' || nodeType === 'FunctionDeclaration' ||
|
|
476
|
+
nodeType === 'method_definition' || nodeType === 'MethodDefinition') {
|
|
477
|
+
return this.extractFunctionDeclarationType(node, depth, maxDepth);
|
|
478
|
+
}
|
|
479
|
+
// Parameter declarations
|
|
480
|
+
if (nodeType === 'required_parameter' || nodeType === 'optional_parameter' ||
|
|
481
|
+
nodeType === 'Identifier') {
|
|
482
|
+
const typeAnnotation = this.findChildByType(node, 'type_annotation') ||
|
|
483
|
+
this.findChildByType(node, 'TSTypeAnnotation');
|
|
484
|
+
if (typeAnnotation) {
|
|
485
|
+
return this.extractTypeFromNode(typeAnnotation, depth + 1, maxDepth);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Extract type reference (named type).
|
|
492
|
+
*/
|
|
493
|
+
extractTypeReference(node, depth, maxDepth) {
|
|
494
|
+
const typeName = this.getTypeName(node);
|
|
495
|
+
const typeArgs = this.extractTypeArguments(node, depth, maxDepth);
|
|
496
|
+
// Check if it's a primitive type
|
|
497
|
+
if (PRIMITIVE_TYPES.has(typeName)) {
|
|
498
|
+
return this.createPrimitiveType(typeName);
|
|
499
|
+
}
|
|
500
|
+
// Check if it's a utility type
|
|
501
|
+
if (UTILITY_TYPES.has(typeName)) {
|
|
502
|
+
const result = {
|
|
503
|
+
kind: 'generic',
|
|
504
|
+
name: typeName,
|
|
505
|
+
text: node.text,
|
|
506
|
+
isNullable: false,
|
|
507
|
+
isOptional: false,
|
|
508
|
+
};
|
|
509
|
+
if (typeArgs.length > 0) {
|
|
510
|
+
result.typeArguments = typeArgs;
|
|
511
|
+
}
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
// Check if we have a definition for this type
|
|
515
|
+
const definition = this.typeDefinitions.get(typeName);
|
|
516
|
+
if (definition) {
|
|
517
|
+
const result = { ...definition };
|
|
518
|
+
if (typeArgs.length > 0) {
|
|
519
|
+
result.typeArguments = typeArgs;
|
|
520
|
+
}
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
// Return as a generic named type
|
|
524
|
+
const kind = this.inferTypeKind(typeName);
|
|
525
|
+
const result = {
|
|
526
|
+
kind,
|
|
527
|
+
name: typeName,
|
|
528
|
+
text: node.text,
|
|
529
|
+
isNullable: false,
|
|
530
|
+
isOptional: false,
|
|
531
|
+
};
|
|
532
|
+
if (typeArgs.length > 0) {
|
|
533
|
+
result.typeArguments = typeArgs;
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Extract union type.
|
|
539
|
+
*/
|
|
540
|
+
extractUnionType(node, depth, maxDepth) {
|
|
541
|
+
const unionTypes = [];
|
|
542
|
+
for (const child of node.children) {
|
|
543
|
+
if (child.type !== '|') {
|
|
544
|
+
const childType = this.extractTypeFromNode(child, depth + 1, maxDepth);
|
|
545
|
+
if (childType) {
|
|
546
|
+
unionTypes.push(childType);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const isNullable = unionTypes.some((t) => t.kind === 'null' || t.kind === 'undefined');
|
|
551
|
+
return {
|
|
552
|
+
kind: 'union',
|
|
553
|
+
text: node.text,
|
|
554
|
+
unionTypes,
|
|
555
|
+
isNullable,
|
|
556
|
+
isOptional: false,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Extract intersection type.
|
|
561
|
+
*/
|
|
562
|
+
extractIntersectionType(node, depth, maxDepth) {
|
|
563
|
+
const intersectionTypes = [];
|
|
564
|
+
for (const child of node.children) {
|
|
565
|
+
if (child.type !== '&') {
|
|
566
|
+
const childType = this.extractTypeFromNode(child, depth + 1, maxDepth);
|
|
567
|
+
if (childType) {
|
|
568
|
+
intersectionTypes.push(childType);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
kind: 'intersection',
|
|
574
|
+
text: node.text,
|
|
575
|
+
intersectionTypes,
|
|
576
|
+
isNullable: false,
|
|
577
|
+
isOptional: false,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Extract array type.
|
|
582
|
+
*/
|
|
583
|
+
extractArrayType(node, depth, maxDepth) {
|
|
584
|
+
const elementTypeNode = node.children[0];
|
|
585
|
+
const elementType = elementTypeNode
|
|
586
|
+
? this.extractTypeFromNode(elementTypeNode, depth + 1, maxDepth)
|
|
587
|
+
: null;
|
|
588
|
+
const result = {
|
|
589
|
+
kind: 'array',
|
|
590
|
+
text: node.text,
|
|
591
|
+
isNullable: false,
|
|
592
|
+
isOptional: false,
|
|
593
|
+
};
|
|
594
|
+
if (elementType) {
|
|
595
|
+
result.elementType = elementType;
|
|
596
|
+
}
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Extract tuple type.
|
|
601
|
+
*/
|
|
602
|
+
extractTupleType(node, depth, maxDepth) {
|
|
603
|
+
const elementTypes = [];
|
|
604
|
+
for (const child of node.children) {
|
|
605
|
+
if (child.type !== '[' && child.type !== ']' && child.type !== ',') {
|
|
606
|
+
const childType = this.extractTypeFromNode(child, depth + 1, maxDepth);
|
|
607
|
+
if (childType) {
|
|
608
|
+
elementTypes.push(childType);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
kind: 'tuple',
|
|
614
|
+
text: node.text,
|
|
615
|
+
typeArguments: elementTypes,
|
|
616
|
+
isNullable: false,
|
|
617
|
+
isOptional: false,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Extract function type.
|
|
622
|
+
*/
|
|
623
|
+
extractFunctionType(node, depth, maxDepth) {
|
|
624
|
+
const parameters = [];
|
|
625
|
+
let returnType = null;
|
|
626
|
+
// Find parameters
|
|
627
|
+
const paramsNode = this.findChildByType(node, 'formal_parameters') ||
|
|
628
|
+
this.findChildByType(node, 'parameters');
|
|
629
|
+
if (paramsNode) {
|
|
630
|
+
for (const param of paramsNode.children) {
|
|
631
|
+
if (param.type === 'required_parameter' || param.type === 'optional_parameter') {
|
|
632
|
+
const paramType = this.extractTypeFromNode(param, depth + 1, maxDepth);
|
|
633
|
+
if (paramType) {
|
|
634
|
+
parameters.push(paramType);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// Find return type
|
|
640
|
+
const returnTypeNode = this.findChildByType(node, 'type_annotation') ||
|
|
641
|
+
this.findChildByType(node, 'return_type');
|
|
642
|
+
if (returnTypeNode) {
|
|
643
|
+
returnType = this.extractTypeFromNode(returnTypeNode, depth + 1, maxDepth);
|
|
644
|
+
}
|
|
645
|
+
const result = {
|
|
646
|
+
kind: 'function',
|
|
647
|
+
text: node.text,
|
|
648
|
+
isNullable: false,
|
|
649
|
+
isOptional: false,
|
|
650
|
+
};
|
|
651
|
+
if (parameters.length > 0) {
|
|
652
|
+
result.parameters = parameters;
|
|
653
|
+
}
|
|
654
|
+
if (returnType) {
|
|
655
|
+
result.returnType = returnType;
|
|
656
|
+
}
|
|
657
|
+
return result;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Extract object type.
|
|
661
|
+
*/
|
|
662
|
+
extractObjectType(node, depth, maxDepth) {
|
|
663
|
+
const properties = [];
|
|
664
|
+
for (const child of node.children) {
|
|
665
|
+
if (child.type === 'property_signature' || child.type === 'TSPropertySignature') {
|
|
666
|
+
const prop = this.extractPropertySignature(child, depth, maxDepth);
|
|
667
|
+
if (prop) {
|
|
668
|
+
properties.push(prop);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const result = {
|
|
673
|
+
kind: 'object',
|
|
674
|
+
text: node.text,
|
|
675
|
+
isNullable: false,
|
|
676
|
+
isOptional: false,
|
|
677
|
+
};
|
|
678
|
+
if (properties.length > 0) {
|
|
679
|
+
result.properties = properties;
|
|
680
|
+
}
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Extract property signature.
|
|
685
|
+
*/
|
|
686
|
+
extractPropertySignature(node, depth, maxDepth) {
|
|
687
|
+
const nameNode = this.findChildByType(node, 'property_identifier') ||
|
|
688
|
+
this.findChildByType(node, 'Identifier');
|
|
689
|
+
if (!nameNode) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
const typeAnnotation = this.findChildByType(node, 'type_annotation') ||
|
|
693
|
+
this.findChildByType(node, 'TSTypeAnnotation');
|
|
694
|
+
const type = typeAnnotation
|
|
695
|
+
? this.extractTypeFromNode(typeAnnotation, depth + 1, maxDepth)
|
|
696
|
+
: this.createTypeInfo('any', 'any');
|
|
697
|
+
const isOptional = node.children.some((c) => c.text === '?');
|
|
698
|
+
const isReadonly = node.children.some((c) => c.type === 'readonly' || c.text === 'readonly');
|
|
699
|
+
return {
|
|
700
|
+
name: nameNode.text,
|
|
701
|
+
type: type || this.createTypeInfo('any', 'any'),
|
|
702
|
+
isOptional,
|
|
703
|
+
isReadonly,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Extract literal type.
|
|
708
|
+
*/
|
|
709
|
+
extractLiteralType(node) {
|
|
710
|
+
const literalNode = node.children[0];
|
|
711
|
+
const text = literalNode?.text || node.text;
|
|
712
|
+
return {
|
|
713
|
+
kind: 'literal',
|
|
714
|
+
text,
|
|
715
|
+
isNullable: false,
|
|
716
|
+
isOptional: false,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Extract type parameter.
|
|
721
|
+
*/
|
|
722
|
+
extractTypeParameter(node) {
|
|
723
|
+
const nameNode = this.findChildByType(node, 'type_identifier') ||
|
|
724
|
+
this.findChildByType(node, 'Identifier');
|
|
725
|
+
const name = nameNode?.text || node.text;
|
|
726
|
+
return {
|
|
727
|
+
kind: 'typeParameter',
|
|
728
|
+
name,
|
|
729
|
+
text: node.text,
|
|
730
|
+
isNullable: false,
|
|
731
|
+
isOptional: false,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Extract function declaration type.
|
|
736
|
+
*/
|
|
737
|
+
extractFunctionDeclarationType(node, depth, maxDepth) {
|
|
738
|
+
const parameters = [];
|
|
739
|
+
let returnType = null;
|
|
740
|
+
// Find parameters
|
|
741
|
+
const paramsNode = this.findChildByType(node, 'formal_parameters') ||
|
|
742
|
+
this.findChildByType(node, 'parameters');
|
|
743
|
+
if (paramsNode) {
|
|
744
|
+
for (const param of paramsNode.children) {
|
|
745
|
+
const paramType = this.extractTypeFromNode(param, depth + 1, maxDepth);
|
|
746
|
+
if (paramType) {
|
|
747
|
+
parameters.push(paramType);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
// Find return type annotation
|
|
752
|
+
const returnTypeAnnotation = this.findChildByType(node, 'type_annotation') ||
|
|
753
|
+
this.findChildByType(node, 'return_type');
|
|
754
|
+
if (returnTypeAnnotation) {
|
|
755
|
+
returnType = this.extractTypeFromNode(returnTypeAnnotation, depth + 1, maxDepth);
|
|
756
|
+
}
|
|
757
|
+
const result = {
|
|
758
|
+
kind: 'function',
|
|
759
|
+
text: node.text,
|
|
760
|
+
isNullable: false,
|
|
761
|
+
isOptional: false,
|
|
762
|
+
};
|
|
763
|
+
if (parameters.length > 0) {
|
|
764
|
+
result.parameters = parameters;
|
|
765
|
+
}
|
|
766
|
+
if (returnType) {
|
|
767
|
+
result.returnType = returnType;
|
|
768
|
+
}
|
|
769
|
+
return result;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Extract type arguments from a generic type.
|
|
773
|
+
*/
|
|
774
|
+
extractTypeArguments(node, depth, maxDepth) {
|
|
775
|
+
const typeArgs = [];
|
|
776
|
+
const typeArgsNode = this.findChildByType(node, 'type_arguments') ||
|
|
777
|
+
this.findChildByType(node, 'TSTypeParameterInstantiation');
|
|
778
|
+
if (typeArgsNode) {
|
|
779
|
+
for (const child of typeArgsNode.children) {
|
|
780
|
+
if (child.type !== '<' && child.type !== '>' && child.type !== ',') {
|
|
781
|
+
const argType = this.extractTypeFromNode(child, depth + 1, maxDepth);
|
|
782
|
+
if (argType) {
|
|
783
|
+
typeArgs.push(argType);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return typeArgs;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Collect type definitions from the AST.
|
|
792
|
+
*/
|
|
793
|
+
collectTypeDefinitions(node) {
|
|
794
|
+
// Type alias declarations
|
|
795
|
+
if (node.type === 'type_alias_declaration' || node.type === 'TSTypeAliasDeclaration') {
|
|
796
|
+
const nameNode = this.findChildByType(node, 'type_identifier') ||
|
|
797
|
+
this.findChildByType(node, 'Identifier');
|
|
798
|
+
if (nameNode) {
|
|
799
|
+
const typeNode = node.children.find((c) => c.type !== 'type_identifier' && c.type !== 'Identifier' &&
|
|
800
|
+
c.type !== '=' && c.type !== 'type');
|
|
801
|
+
if (typeNode) {
|
|
802
|
+
const typeInfo = this.extractTypeFromNode(typeNode, 0, 10);
|
|
803
|
+
if (typeInfo) {
|
|
804
|
+
this.typeDefinitions.set(nameNode.text, {
|
|
805
|
+
...typeInfo,
|
|
806
|
+
name: nameNode.text,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// Interface declarations
|
|
813
|
+
if (node.type === 'interface_declaration' || node.type === 'TSInterfaceDeclaration') {
|
|
814
|
+
const nameNode = this.findChildByType(node, 'type_identifier') ||
|
|
815
|
+
this.findChildByType(node, 'Identifier');
|
|
816
|
+
if (nameNode) {
|
|
817
|
+
this.typeDefinitions.set(nameNode.text, {
|
|
818
|
+
kind: 'interface',
|
|
819
|
+
name: nameNode.text,
|
|
820
|
+
text: node.text,
|
|
821
|
+
isNullable: false,
|
|
822
|
+
isOptional: false,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
// Class declarations
|
|
827
|
+
if (node.type === 'class_declaration' || node.type === 'ClassDeclaration') {
|
|
828
|
+
const nameNode = this.findChildByType(node, 'type_identifier') ||
|
|
829
|
+
this.findChildByType(node, 'Identifier');
|
|
830
|
+
if (nameNode) {
|
|
831
|
+
this.typeDefinitions.set(nameNode.text, {
|
|
832
|
+
kind: 'class',
|
|
833
|
+
name: nameNode.text,
|
|
834
|
+
text: node.text,
|
|
835
|
+
isNullable: false,
|
|
836
|
+
isOptional: false,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// Enum declarations
|
|
841
|
+
if (node.type === 'enum_declaration' || node.type === 'TSEnumDeclaration') {
|
|
842
|
+
const nameNode = this.findChildByType(node, 'identifier') ||
|
|
843
|
+
this.findChildByType(node, 'Identifier');
|
|
844
|
+
if (nameNode) {
|
|
845
|
+
this.typeDefinitions.set(nameNode.text, {
|
|
846
|
+
kind: 'enum',
|
|
847
|
+
name: nameNode.text,
|
|
848
|
+
text: node.text,
|
|
849
|
+
isNullable: false,
|
|
850
|
+
isOptional: false,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
// Recurse into children
|
|
855
|
+
for (const child of node.children) {
|
|
856
|
+
this.collectTypeDefinitions(child);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Traverse AST for type analysis.
|
|
861
|
+
*/
|
|
862
|
+
traverseForTypes(node, types, errors, options) {
|
|
863
|
+
// Extract type from this node if applicable
|
|
864
|
+
const typeInfo = this.extractTypeFromNode(node, 0, 10);
|
|
865
|
+
if (typeInfo) {
|
|
866
|
+
const key = `${node.startPosition.row}:${node.startPosition.column}`;
|
|
867
|
+
types.set(key, typeInfo);
|
|
868
|
+
}
|
|
869
|
+
// Check for type errors
|
|
870
|
+
if (options.deep) {
|
|
871
|
+
this.checkTypeErrors(node, errors);
|
|
872
|
+
}
|
|
873
|
+
// Recurse into children
|
|
874
|
+
for (const child of node.children) {
|
|
875
|
+
this.traverseForTypes(child, types, errors, options);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Check for type errors in a node.
|
|
880
|
+
*/
|
|
881
|
+
checkTypeErrors(node, errors) {
|
|
882
|
+
// Check for explicit 'any' usage
|
|
883
|
+
if (node.type === 'predefined_type' && node.text === 'any') {
|
|
884
|
+
errors.push({
|
|
885
|
+
message: "Explicit 'any' type usage",
|
|
886
|
+
location: {
|
|
887
|
+
start: node.startPosition,
|
|
888
|
+
end: node.endPosition,
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
// Check for type assertions to 'any'
|
|
893
|
+
if ((node.type === 'as_expression' || node.type === 'TSAsExpression') &&
|
|
894
|
+
node.text.includes(' as any')) {
|
|
895
|
+
errors.push({
|
|
896
|
+
message: "Type assertion to 'any'",
|
|
897
|
+
location: {
|
|
898
|
+
start: node.startPosition,
|
|
899
|
+
end: node.endPosition,
|
|
900
|
+
},
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Collect typeable locations from AST.
|
|
906
|
+
*/
|
|
907
|
+
collectTypeableLocations(node) {
|
|
908
|
+
const locations = [];
|
|
909
|
+
this.traverseNode(node, (n) => {
|
|
910
|
+
// Variable declarations
|
|
911
|
+
if (n.type === 'variable_declarator' || n.type === 'VariableDeclarator') {
|
|
912
|
+
const hasTypeAnnotation = this.findChildByType(n, 'type_annotation') ||
|
|
913
|
+
this.findChildByType(n, 'TSTypeAnnotation');
|
|
914
|
+
const initializerNode = this.findChildByType(n, 'initializer');
|
|
915
|
+
const hasInitializer = initializerNode !== null || n.children.some((c) => c.type === '=');
|
|
916
|
+
locations.push({
|
|
917
|
+
node: n,
|
|
918
|
+
location: { start: n.startPosition, end: n.endPosition },
|
|
919
|
+
isInferred: !hasTypeAnnotation && hasInitializer,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
// Function parameters
|
|
923
|
+
if (n.type === 'required_parameter' || n.type === 'optional_parameter') {
|
|
924
|
+
const hasTypeAnnotation = this.findChildByType(n, 'type_annotation') ||
|
|
925
|
+
this.findChildByType(n, 'TSTypeAnnotation');
|
|
926
|
+
locations.push({
|
|
927
|
+
node: n,
|
|
928
|
+
location: { start: n.startPosition, end: n.endPosition },
|
|
929
|
+
isInferred: !hasTypeAnnotation,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
// Function return types
|
|
933
|
+
if (n.type === 'function_declaration' || n.type === 'FunctionDeclaration' ||
|
|
934
|
+
n.type === 'arrow_function' || n.type === 'ArrowFunctionExpression') {
|
|
935
|
+
const hasReturnType = this.findChildByType(n, 'type_annotation') ||
|
|
936
|
+
this.findChildByType(n, 'return_type');
|
|
937
|
+
locations.push({
|
|
938
|
+
node: n,
|
|
939
|
+
location: { start: n.startPosition, end: n.endPosition },
|
|
940
|
+
isInferred: !hasReturnType,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
return locations;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Calculate coverage from analyzed types.
|
|
948
|
+
*/
|
|
949
|
+
calculateCoverageFromTypes(types) {
|
|
950
|
+
if (types.size === 0) {
|
|
951
|
+
return 100;
|
|
952
|
+
}
|
|
953
|
+
let typedCount = 0;
|
|
954
|
+
let anyCount = 0;
|
|
955
|
+
for (const typeInfo of types.values()) {
|
|
956
|
+
if (typeInfo.kind === 'any') {
|
|
957
|
+
anyCount++;
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
typedCount++;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return (typedCount / types.size) * 100;
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Check if object type1 is a subtype of object type2.
|
|
967
|
+
*/
|
|
968
|
+
isObjectSubtype(type1, type2) {
|
|
969
|
+
if (!type2.properties) {
|
|
970
|
+
return true; // Empty object type is supertype of all objects
|
|
971
|
+
}
|
|
972
|
+
if (!type1.properties) {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
// All properties in type2 must exist in type1 with compatible types
|
|
976
|
+
for (const prop2 of type2.properties) {
|
|
977
|
+
const prop1 = type1.properties.find((p) => p.name === prop2.name);
|
|
978
|
+
if (!prop1) {
|
|
979
|
+
if (!prop2.isOptional) {
|
|
980
|
+
return false; // Required property missing
|
|
981
|
+
}
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (!this.isSubtypeOf(prop1.type, prop2.type)) {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Check if function type1 is a subtype of function type2.
|
|
992
|
+
*/
|
|
993
|
+
isFunctionSubtype(type1, type2) {
|
|
994
|
+
// Check return type (covariant)
|
|
995
|
+
if (type1.returnType && type2.returnType) {
|
|
996
|
+
if (!this.isSubtypeOf(type1.returnType, type2.returnType)) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
// Check parameters (contravariant)
|
|
1001
|
+
const params1 = type1.parameters || [];
|
|
1002
|
+
const params2 = type2.parameters || [];
|
|
1003
|
+
// type1 can have fewer required parameters
|
|
1004
|
+
for (let i = 0; i < params2.length; i++) {
|
|
1005
|
+
const param1 = params1[i];
|
|
1006
|
+
const param2 = params2[i];
|
|
1007
|
+
if (!param1) {
|
|
1008
|
+
if (!param2?.isOptional) {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (param2 && !this.isSubtypeOf(param2, param1)) {
|
|
1014
|
+
return false; // Contravariant
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return true;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Check if two function types are equivalent.
|
|
1021
|
+
*/
|
|
1022
|
+
areFunctionTypesEquivalent(type1, type2) {
|
|
1023
|
+
// Check return types
|
|
1024
|
+
if (type1.returnType && type2.returnType) {
|
|
1025
|
+
if (!this.areTypesEquivalent(type1.returnType, type2.returnType)) {
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
else if (type1.returnType !== type2.returnType) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
// Check parameters
|
|
1033
|
+
const params1 = type1.parameters || [];
|
|
1034
|
+
const params2 = type2.parameters || [];
|
|
1035
|
+
if (params1.length !== params2.length) {
|
|
1036
|
+
return false;
|
|
1037
|
+
}
|
|
1038
|
+
for (let i = 0; i < params1.length; i++) {
|
|
1039
|
+
if (!this.areTypesEquivalent(params1[i], params2[i])) {
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Check if two object types are equivalent.
|
|
1047
|
+
*/
|
|
1048
|
+
areObjectTypesEquivalent(type1, type2) {
|
|
1049
|
+
const props1 = type1.properties || [];
|
|
1050
|
+
const props2 = type2.properties || [];
|
|
1051
|
+
if (props1.length !== props2.length) {
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
for (const prop1 of props1) {
|
|
1055
|
+
const prop2 = props2.find((p) => p.name === prop1.name);
|
|
1056
|
+
if (!prop2) {
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
if (prop1.isOptional !== prop2.isOptional ||
|
|
1060
|
+
prop1.isReadonly !== prop2.isReadonly) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
if (!this.areTypesEquivalent(prop1.type, prop2.type)) {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return true;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Get the primitive type for a literal type.
|
|
1071
|
+
*/
|
|
1072
|
+
getLiteralPrimitiveType(type) {
|
|
1073
|
+
const text = type.text;
|
|
1074
|
+
if (text.startsWith('"') || text.startsWith("'") || text.startsWith('`')) {
|
|
1075
|
+
return 'string';
|
|
1076
|
+
}
|
|
1077
|
+
if (/^-?\d+(\.\d+)?$/.test(text)) {
|
|
1078
|
+
return 'number';
|
|
1079
|
+
}
|
|
1080
|
+
if (text === 'true' || text === 'false') {
|
|
1081
|
+
return 'boolean';
|
|
1082
|
+
}
|
|
1083
|
+
if (text.endsWith('n')) {
|
|
1084
|
+
return 'bigint';
|
|
1085
|
+
}
|
|
1086
|
+
return 'unknown';
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Traverse AST nodes with a callback.
|
|
1090
|
+
*/
|
|
1091
|
+
traverseNode(node, callback) {
|
|
1092
|
+
callback(node);
|
|
1093
|
+
for (const child of node.children) {
|
|
1094
|
+
this.traverseNode(child, callback);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Find a child node by type.
|
|
1099
|
+
*/
|
|
1100
|
+
findChildByType(node, type) {
|
|
1101
|
+
for (const child of node.children) {
|
|
1102
|
+
if (child.type === type) {
|
|
1103
|
+
return child;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Get type name from a type reference node.
|
|
1110
|
+
*/
|
|
1111
|
+
getTypeName(node) {
|
|
1112
|
+
const identifierNode = this.findChildByType(node, 'type_identifier') ||
|
|
1113
|
+
this.findChildByType(node, 'Identifier');
|
|
1114
|
+
if (identifierNode) {
|
|
1115
|
+
return identifierNode.text;
|
|
1116
|
+
}
|
|
1117
|
+
// For simple type identifiers
|
|
1118
|
+
if (node.type === 'type_identifier' || node.type === 'Identifier') {
|
|
1119
|
+
return node.text;
|
|
1120
|
+
}
|
|
1121
|
+
// Extract name from text (remove type arguments)
|
|
1122
|
+
const text = node.text;
|
|
1123
|
+
const angleIndex = text.indexOf('<');
|
|
1124
|
+
return angleIndex > 0 ? text.substring(0, angleIndex) : text;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Infer type kind from name.
|
|
1128
|
+
*/
|
|
1129
|
+
inferTypeKind(name) {
|
|
1130
|
+
// Interface naming convention (I prefix)
|
|
1131
|
+
if (name.startsWith('I') && name.length > 1 && name[1] === name[1]?.toUpperCase()) {
|
|
1132
|
+
return 'interface';
|
|
1133
|
+
}
|
|
1134
|
+
// Type naming convention (T prefix for type parameters)
|
|
1135
|
+
if (name.length === 1 && name >= 'A' && name <= 'Z') {
|
|
1136
|
+
return 'typeParameter';
|
|
1137
|
+
}
|
|
1138
|
+
// Default to class for PascalCase names
|
|
1139
|
+
if (name[0] === name[0]?.toUpperCase()) {
|
|
1140
|
+
return 'class';
|
|
1141
|
+
}
|
|
1142
|
+
return 'unknown';
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Create a primitive type info.
|
|
1146
|
+
*/
|
|
1147
|
+
createPrimitiveType(name) {
|
|
1148
|
+
const kind = this.getPrimitiveKind(name);
|
|
1149
|
+
return {
|
|
1150
|
+
kind,
|
|
1151
|
+
name,
|
|
1152
|
+
text: name,
|
|
1153
|
+
isNullable: name === 'null',
|
|
1154
|
+
isOptional: false,
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Get the TypeKind for a primitive type name.
|
|
1159
|
+
*/
|
|
1160
|
+
getPrimitiveKind(name) {
|
|
1161
|
+
switch (name) {
|
|
1162
|
+
case 'null':
|
|
1163
|
+
return 'null';
|
|
1164
|
+
case 'undefined':
|
|
1165
|
+
return 'undefined';
|
|
1166
|
+
case 'void':
|
|
1167
|
+
return 'void';
|
|
1168
|
+
case 'never':
|
|
1169
|
+
return 'never';
|
|
1170
|
+
case 'any':
|
|
1171
|
+
return 'any';
|
|
1172
|
+
case 'unknown':
|
|
1173
|
+
return 'unknown';
|
|
1174
|
+
default:
|
|
1175
|
+
return 'primitive';
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Create a basic type info.
|
|
1180
|
+
*/
|
|
1181
|
+
createTypeInfo(kind, text) {
|
|
1182
|
+
return {
|
|
1183
|
+
kind,
|
|
1184
|
+
text,
|
|
1185
|
+
isNullable: false,
|
|
1186
|
+
isOptional: false,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Create a named type info.
|
|
1191
|
+
*/
|
|
1192
|
+
createNamedType(name, kind) {
|
|
1193
|
+
return {
|
|
1194
|
+
kind,
|
|
1195
|
+
name,
|
|
1196
|
+
text: name,
|
|
1197
|
+
isNullable: false,
|
|
1198
|
+
isOptional: false,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Get class name from a class declaration node.
|
|
1203
|
+
*/
|
|
1204
|
+
getClassName(node) {
|
|
1205
|
+
const nameNode = this.findChildByType(node, 'type_identifier') ||
|
|
1206
|
+
this.findChildByType(node, 'Identifier');
|
|
1207
|
+
return nameNode?.text || null;
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Get interface name from an interface declaration node.
|
|
1211
|
+
*/
|
|
1212
|
+
getInterfaceName(node) {
|
|
1213
|
+
const nameNode = this.findChildByType(node, 'type_identifier') ||
|
|
1214
|
+
this.findChildByType(node, 'Identifier');
|
|
1215
|
+
return nameNode?.text || null;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Get the extended class/interface name from an extends clause.
|
|
1219
|
+
*/
|
|
1220
|
+
getExtendsName(node) {
|
|
1221
|
+
const typeNode = this.findChildByType(node, 'type_identifier') ||
|
|
1222
|
+
this.findChildByType(node, 'Identifier') ||
|
|
1223
|
+
this.findChildByType(node, 'generic_type');
|
|
1224
|
+
if (typeNode) {
|
|
1225
|
+
return this.getTypeName(typeNode);
|
|
1226
|
+
}
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Get all extended interface names from an extends clause.
|
|
1231
|
+
*/
|
|
1232
|
+
getExtendsNames(node) {
|
|
1233
|
+
const names = [];
|
|
1234
|
+
for (const child of node.children) {
|
|
1235
|
+
if (child.type === 'type_identifier' || child.type === 'Identifier' ||
|
|
1236
|
+
child.type === 'generic_type') {
|
|
1237
|
+
const name = this.getTypeName(child);
|
|
1238
|
+
if (name) {
|
|
1239
|
+
names.push(name);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return names;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Get implemented interface names from an implements clause.
|
|
1247
|
+
*/
|
|
1248
|
+
getImplementsNames(node) {
|
|
1249
|
+
const names = [];
|
|
1250
|
+
for (const child of node.children) {
|
|
1251
|
+
if (child.type === 'type_identifier' || child.type === 'Identifier' ||
|
|
1252
|
+
child.type === 'generic_type') {
|
|
1253
|
+
const name = this.getTypeName(child);
|
|
1254
|
+
if (name) {
|
|
1255
|
+
names.push(name);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return names;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Clear internal caches.
|
|
1263
|
+
*/
|
|
1264
|
+
clearCache() {
|
|
1265
|
+
this.typeCache.clear();
|
|
1266
|
+
this.typeDefinitions.clear();
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
//# sourceMappingURL=type-analyzer.js.map
|