pkg-scaffold 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,325 +1,266 @@
1
1
  import ts from 'typescript';
2
2
  import fs from 'fs/promises';
3
- import crypto from 'crypto';
3
+ import path from 'path';
4
4
 
5
5
  /**
6
6
  * Enterprise AST Syntax Walker & Feature Extractor
7
- * Utilizes the official TypeScript Compiler infrastructure to execute deeply nested
8
- * node classification without falling back to high-risk regular expression approximations.
7
+ * Upgraded to use full TypeScript Compiler API (ts.createProgram + TypeChecker)
8
+ * for type-aware cross-file analysis.
9
9
  */
10
+ import { OxcAnalyzer } from './OxcAnalyzer.js';
11
+
10
12
  export class ASTAnalyzer {
11
13
  constructor(context) {
12
14
  this.context = context;
13
- // Standard high-entropy baseline selectors for AST variable tracking
14
- this.entropyThreshold = 4.3;
15
+ this.program = null;
16
+ this.checker = null;
17
+ this.oxc = new OxcAnalyzer(context);
15
18
  }
16
19
 
17
20
  /**
18
- * Parses target file data into an isolated AST representation and populates metadata structures.
19
- * @param {string} filePath - Absolute path to on-disk component
20
- * @param {Object} fileNode - In-memory structural graph reference node
21
+ * Initializes the TypeScript program for the entire project.
22
+ * This is crucial for cross-file type resolution.
23
+ */
24
+ initProgram(filePaths, options = {}) {
25
+ const defaultOptions = {
26
+ target: ts.ScriptTarget.Latest,
27
+ module: ts.ModuleKind.CommonJS,
28
+ allowJs: true,
29
+ checkJs: true,
30
+ esModuleInterop: true,
31
+ skipLibCheck: true
32
+ };
33
+
34
+ this.program = ts.createProgram(filePaths, { ...defaultOptions, ...options });
35
+ this.checker = this.program.getTypeChecker();
36
+ }
37
+
38
+ /**
39
+ * Processes a file using the initialized program and type checker.
21
40
  */
22
41
  async processFile(filePath, fileNode) {
23
- try {
24
- const sourceText = await fs.readFile(filePath, 'utf8');
25
-
26
- // Configure target extraction structures to parse TS, JSX, and modern TC39 specifications
27
- const sourceFile = ts.createSourceFile(
28
- filePath,
29
- sourceText,
30
- ts.ScriptTarget.Latest,
31
- true, // Ensure parent pointers are bound to allow localized subtree walking
32
- this.getScriptKind(filePath)
33
- );
42
+ // Fast Path: Use OXC for rapid scanning if type checking is not strictly required for this file
43
+ if (this.context.fastMode) {
44
+ return await this.oxc.processFile(filePath, fileNode);
45
+ }
34
46
 
35
- this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
36
- this.walkNode(sourceFile, sourceFile, fileNode);
37
-
38
- return true;
39
- } catch (parseError) {
47
+ if (!this.program) {
48
+ throw new Error('ASTAnalyzer must be initialized with initProgram() before processing files.');
49
+ }
50
+
51
+ const sourceFile = this.program.getSourceFile(filePath);
52
+ if (!sourceFile) {
40
53
  if (this.context.verbose) {
41
- console.error(`[AST Open Error] Failed compilation validation mapping on element: ${filePath}. Reason: ${parseError.message}`);
54
+ console.error(`[AST Error] Source file not found in program: ${filePath}`);
42
55
  }
43
56
  return false;
44
57
  }
58
+
59
+ this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
60
+ this.walkNode(sourceFile, sourceFile, fileNode);
61
+
62
+ return true;
45
63
  }
46
64
 
47
- /**
48
- * Primary node walker loop executing atomic switch classifications.
49
- * Challenge #7: Resolves conditional/destructured references to prevent cascading breakages.
50
- */
51
65
  walkNode(sourceFile, node, fileNode) {
52
66
  if (!node) return;
53
67
 
54
- switch (node.kind) {
55
- // Handle Explicit Named or Absolute Star Namespace Imports
56
- case ts.SyntaxKind.ImportDeclaration: {
57
- if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
58
- const specifier = node.moduleSpecifier.text;
59
- fileNode.explicitImports.add(specifier);
60
-
61
- if (node.importClause) {
62
- // Trace named bounds: import { activeToken } from 'module';
63
- if (node.importClause.namedBindings) {
64
- if (ts.isNamedImports(node.importClause.namedBindings)) {
65
- node.importClause.namedBindings.elements.forEach(element => {
66
- const importedName = element.name.text;
67
- const propertyName = element.propertyName ? element.propertyName.text : importedName;
68
- fileNode.importedSymbols.add(`${specifier}:${propertyName}`);
69
- });
70
- } else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
71
- // Tracking total wildcard imports: import * as layout from 'module';
72
- fileNode.importedSymbols.add(`${specifier}:*`);
73
- }
74
- }
75
- // Trace default bounds: import React from 'react';
76
- if (node.importClause.name) {
77
- fileNode.importedSymbols.add(`${specifier}:default`);
78
- }
68
+ // Use type checker to resolve symbols if needed
69
+ if (ts.isIdentifier(node) && !this.isNodeDeclarationName(node)) {
70
+ const symbol = this.checker.getSymbolAtLocation(node);
71
+ if (symbol) {
72
+ const declarations = symbol.getDeclarations();
73
+ if (declarations && declarations.length > 0) {
74
+ const declFile = declarations[0].getSourceFile().fileName;
75
+ const symbolName = symbol.getName();
76
+
77
+ // Track sub-symbol usage (Property Access)
78
+ if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
79
+ const parentType = this.checker.getTypeAtLocation(node.parent.expression);
80
+ const parentSymbol = parentType.getSymbol() || parentType.aliasSymbol;
81
+ if (parentSymbol) {
82
+ const parentDecl = parentSymbol.getDeclarations()?.[0];
83
+ if (parentDecl) {
84
+ const parentFile = parentDecl.getSourceFile().fileName;
85
+ fileNode.memberUsage = fileNode.memberUsage || new Set();
86
+ fileNode.memberUsage.add(`${parentFile}:${parentSymbol.getName()}.${symbolName}`);
87
+ }
88
+ }
79
89
  }
80
- }
81
- break;
82
- }
83
90
 
84
- // Handle Explicit Namespace Requirements: import config = require('./config');
85
- case ts.SyntaxKind.ImportEqualsDeclaration: {
86
- if (node.moduleReference && ts.isExternalModuleReference(node.moduleReference)) {
87
- if (node.moduleReference.expression && ts.isStringLiteral(node.moduleReference.expression)) {
88
- fileNode.explicitImports.add(node.moduleReference.expression.text);
91
+ if (declFile !== sourceFile.fileName) {
92
+ fileNode.resolvedReferences = fileNode.resolvedReferences || new Set();
93
+ fileNode.resolvedReferences.add(`${declFile}:${symbolName}`);
89
94
  }
90
95
  }
91
- break;
92
96
  }
97
+ }
93
98
 
94
- // Challenge #1: Tracking dynamic expressions e.g., import('./chunks/' + variant)
95
- case ts.SyntaxKind.CallExpression: {
96
- if (node.expression && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
97
- const firstArgument = node.arguments[0];
98
- if (firstArgument) {
99
- if (ts.isStringLiteral(firstArgument)) {
100
- fileNode.explicitImports.add(firstArgument.text);
101
- fileNode.dynamicImports.add(firstArgument.text);
102
- } else {
103
- // Deeply trace runtime calculated variables within the import parameters call
104
- const stringPatterns = [];
105
- this.traceStringExpressions(firstArgument, stringPatterns);
106
- fileNode.calculatedDynamicImports.push({
107
- rawText: firstArgument.getText(sourceFile),
108
- heuristics: stringPatterns,
109
- position: node.getStart(sourceFile)
110
- });
111
- }
112
- }
113
- }
99
+ switch (node.kind) {
100
+ case ts.SyntaxKind.ImportDeclaration: {
101
+ this.handleImportDeclaration(node, fileNode);
114
102
  break;
115
103
  }
116
-
117
- // Handle Variable Declaration Assignments & Challenge #11 (AST Secret Scanning)
118
- case ts.SyntaxKind.VariableDeclaration: {
119
- if (node.name && ts.isIdentifier(node.name)) {
120
- this.auditAssignmentSafety(node.name.text, node.initializer, fileNode, sourceFile);
121
- } else if (node.name && (ts.isObjectBindingPattern(node.name) || ts.isArrayBindingPattern(node.name))) {
122
- // Flatten binding properties to map destructured usage accurately
123
- node.name.elements.forEach(element => {
124
- if (element.name && ts.isIdentifier(element.name)) {
125
- this.auditAssignmentSafety(element.name.text, null, fileNode, sourceFile);
126
- }
127
- });
128
- }
104
+ case ts.SyntaxKind.ExportDeclaration: {
105
+ this.handleExportDeclaration(node, fileNode, sourceFile);
129
106
  break;
130
107
  }
131
-
132
- // Handle Named Function Node Exports Matrix Configurations
133
- // If the function carries both ExportKeyword AND DefaultKeyword modifiers, it is an
134
- // `export default function` declaration. In that case the canonical export name is
135
- // 'default', not the function's identifier, so that barrel-file tracing (which looks
136
- // up 'default' when following `export { default as X } from './y'`) resolves correctly.
137
- case ts.SyntaxKind.FunctionDeclaration: {
138
- if (this.hasExportModifier(node)) {
139
- const isDefaultExport = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
140
- if (isDefaultExport) {
141
- // Register under 'default'; also store the real name as referencedSymbol for diagnostics
142
- const realName = node.name && ts.isIdentifier(node.name) ? node.name.text : 'anonymous';
143
- fileNode.internalExports.set('default', { type: 'default-function', referencedSymbol: realName, start: node.getStart(sourceFile), end: node.getEnd() });
144
- } else if (node.name && ts.isIdentifier(node.name)) {
145
- fileNode.internalExports.set(node.name.text, { type: 'function', start: node.getStart(sourceFile), end: node.getEnd() });
146
- }
147
- }
108
+ case ts.SyntaxKind.FunctionDeclaration:
109
+ case ts.SyntaxKind.ClassDeclaration:
110
+ case ts.SyntaxKind.InterfaceDeclaration:
111
+ case ts.SyntaxKind.TypeAliasDeclaration:
112
+ case ts.SyntaxKind.EnumDeclaration:
113
+ case ts.SyntaxKind.ModuleDeclaration: {
114
+ this.handleNamedDeclaration(node, fileNode, sourceFile);
148
115
  break;
149
116
  }
150
-
151
- // Handle Structural Class Definitions and Class Export Signatures
152
- case ts.SyntaxKind.ClassDeclaration: {
153
- if (this.hasExportModifier(node)) {
154
- const isDefaultExport = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
155
- if (isDefaultExport) {
156
- const realName = node.name && ts.isIdentifier(node.name) ? node.name.text : 'anonymous';
157
- fileNode.internalExports.set('default', { type: 'default-class', referencedSymbol: realName, start: node.getStart(sourceFile), end: node.getEnd() });
158
- } else if (node.name && ts.isIdentifier(node.name)) {
159
- fileNode.internalExports.set(node.name.text, { type: 'class', start: node.getStart(sourceFile), end: node.getEnd() });
160
- }
161
- }
117
+ case ts.SyntaxKind.VariableStatement: {
118
+ this.handleVariableStatement(node, fileNode, sourceFile);
162
119
  break;
163
120
  }
164
-
165
- // Handle Interface Definitions (Crucial for Challenge #10: Type Integrity Mapping)
166
- case ts.SyntaxKind.InterfaceDeclaration: {
167
- if (node.name && ts.isIdentifier(node.name)) {
168
- const name = node.name.text;
169
- if (this.hasExportModifier(node)) {
170
- fileNode.internalExports.set(name, { type: 'interface', start: node.getStart(sourceFile), end: node.getEnd() });
171
- }
172
- }
121
+ case ts.SyntaxKind.CallExpression: {
122
+ this.handleCallExpression(node, fileNode, sourceFile);
173
123
  break;
174
124
  }
125
+ }
126
+
127
+ ts.forEachChild(node, child => this.walkNode(sourceFile, child, fileNode));
128
+ }
175
129
 
176
- // Handle Type Invocations and Declarations Aliases
177
- case ts.SyntaxKind.TypeAliasDeclaration: {
178
- if (node.name && ts.isIdentifier(node.name)) {
179
- const name = node.name.text;
180
- if (this.hasExportModifier(node)) {
181
- fileNode.internalExports.set(name, { type: 'type-alias', start: node.getStart(sourceFile), end: node.getEnd() });
130
+ handleImportDeclaration(node, fileNode) {
131
+ if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
132
+ const specifier = node.moduleSpecifier.text;
133
+ fileNode.explicitImports.add(specifier);
134
+
135
+ if (node.importClause) {
136
+ if (node.importClause.namedBindings) {
137
+ if (ts.isNamedImports(node.importClause.namedBindings)) {
138
+ node.importClause.namedBindings.elements.forEach(element => {
139
+ const importedName = element.name.text;
140
+ const propertyName = element.propertyName ? element.propertyName.text : importedName;
141
+ fileNode.importedSymbols.add(`${specifier}:${propertyName}`);
142
+ });
143
+ } else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
144
+ fileNode.importedSymbols.add(`${specifier}:*`);
182
145
  }
183
146
  }
184
- break;
147
+ if (node.importClause.name) {
148
+ fileNode.importedSymbols.add(`${specifier}:default`);
149
+ }
185
150
  }
151
+ }
152
+ }
186
153
 
187
- // Handle Explicit Export Assignments: export default baselineConfiguration;
188
- case ts.SyntaxKind.ExportAssignment: {
189
- const name = node.expression ? node.expression.getText(sourceFile) : 'default';
190
- fileNode.internalExports.set('default', {
191
- type: 'default-assignment',
192
- referencedSymbol: name,
193
- start: node.getStart(sourceFile),
194
- end: node.getEnd()
154
+ handleExportDeclaration(node, fileNode, sourceFile) {
155
+ // Handle re-exports: export { x } from './y'
156
+ if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
157
+ const specifier = node.moduleSpecifier.text;
158
+ if (node.exportClause && ts.isNamedExports(node.exportClause)) {
159
+ node.exportClause.elements.forEach(element => {
160
+ const name = element.name.text;
161
+ fileNode.internalExports.set(name, {
162
+ type: 're-export',
163
+ source: specifier,
164
+ start: node.getStart(sourceFile),
165
+ end: node.getEnd()
166
+ });
195
167
  });
196
- break;
168
+ } else {
169
+ // export * from './y'
170
+ fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
197
171
  }
172
+ }
173
+ }
198
174
 
199
- // Handle Arbitrary String References to catch deep framework routing or dynamic keys
200
- case ts.SyntaxKind.StringLiteral: {
201
- const text = node.text;
202
- if (text.length > 2 && text.length < 120) {
203
- fileNode.rawStringReferences.add(text);
204
- }
205
- break;
206
- }
175
+ handleNamedDeclaration(node, fileNode, sourceFile) {
176
+ if (this.hasExportModifier(node)) {
177
+ const isDefault = node.modifiers?.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
178
+ const name = isDefault ? 'default' : (node.name?.text || 'anonymous');
179
+
180
+ const exportInfo = {
181
+ type: ts.SyntaxKind[node.kind].toLowerCase().replace('declaration', ''),
182
+ start: node.getStart(sourceFile),
183
+ end: node.getEnd()
184
+ };
207
185
 
208
- // Track general identifiers to register references to mapped import keys
209
- case ts.SyntaxKind.Identifier: {
210
- const idText = node.text;
211
- // Avoid adding declarations to usage logs to keep verification accurate
212
- if (!this.isNodeDeclarationName(node)) {
213
- fileNode.instantiatedIdentifiers.add(idText);
186
+ fileNode.internalExports.set(name, exportInfo);
187
+
188
+ // Phase 4: Drill down into members
189
+ if (ts.isEnumDeclaration(node)) {
190
+ exportInfo.members = node.members.map(m => m.name.getText(sourceFile));
191
+ } else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
192
+ exportInfo.members = node.members
193
+ .filter(m => m.name)
194
+ .map(m => m.name.getText(sourceFile));
195
+ } else if (ts.isModuleDeclaration(node)) {
196
+ // Handle Namespaces
197
+ const members = [];
198
+ if (node.body && ts.isModuleBlock(node.body)) {
199
+ node.body.statements.forEach(stmt => {
200
+ if (this.hasExportModifier(stmt) && (ts.isVariableStatement(stmt) || ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt))) {
201
+ if (ts.isVariableStatement(stmt)) {
202
+ stmt.declarationList.declarations.forEach(d => members.push(d.name.getText(sourceFile)));
203
+ } else if (stmt.name) {
204
+ members.push(stmt.name.getText(sourceFile));
205
+ }
206
+ }
207
+ });
214
208
  }
215
- break;
209
+ exportInfo.members = members;
216
210
  }
217
- }
218
211
 
219
- // Traverse recursively down the Node structural tree
220
- ts.forEachChild(node, child => this.walkNode(sourceFile, child, fileNode));
212
+ const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
213
+ fileNode.symbolSourceLocations.set(name, { line: loc.line + 1, column: loc.character + 1 });
214
+ }
221
215
  }
222
216
 
223
- /**
224
- * Challenge #1: Evaluates math operations and template configurations inside dynamic imports.
225
- */
226
- traceStringExpressions(node, collector) {
227
- if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
228
- this.traceStringExpressions(node.left, collector);
229
- this.traceStringExpressions(node.right, collector);
230
- } else if (ts.isStringLiteral(node)) {
231
- collector.push({ type: 'literal', val: node.text });
232
- } else if (ts.isTemplateExpression(node)) {
233
- if (node.head) collector.push({ type: 'template-slice', val: node.head.text });
234
- node.templateSpans.forEach(span => {
235
- collector.push({ type: 'dynamic-var', val: span.expression.getText() });
236
- if (span.literal) collector.push({ type: 'template-slice', val: span.literal.text });
217
+ handleVariableStatement(node, fileNode, sourceFile) {
218
+ if (this.hasExportModifier(node)) {
219
+ node.declarationList.declarations.forEach(decl => {
220
+ if (decl.name && ts.isIdentifier(decl.name)) {
221
+ const name = decl.name.text;
222
+ fileNode.internalExports.set(name, {
223
+ type: 'variable',
224
+ start: decl.getStart(sourceFile),
225
+ end: decl.getEnd()
226
+ });
227
+ }
237
228
  });
238
- } else {
239
- collector.push({ type: 'computed-variable', val: node.getText() });
240
229
  }
241
230
  }
242
231
 
243
- /**
244
- * Challenge #11: AST Secret Scanning. Evaluates entropy and patterns directly via assignments.
245
- */
246
- auditAssignmentSafety(variableName, initializer, fileNode, sourceFile) {
247
- // Process variable mapping target indicators first
248
- if (this.hasExportModifier(variableName.parent)) {
249
- fileNode.internalExports.set(variableName.text, { type: 'variable', start: variableName.parent.getStart(sourceFile), end: variableName.parent.getEnd() });
232
+ handleCallExpression(node, fileNode, sourceFile) {
233
+ // Trace dynamic imports
234
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
235
+ const arg = node.arguments[0];
236
+ if (arg && ts.isStringLiteral(arg)) {
237
+ fileNode.explicitImports.add(arg.text);
238
+ fileNode.dynamicImports.add(arg.text);
239
+ }
250
240
  }
241
+ }
251
242
 
252
- if (!initializer || !ts.isStringLiteral(initializer)) return;
253
- const value = initializer.text;
254
-
255
- // Challenge #11 Heuristic validation parameters matching variable patterns or contents values
256
- const isSuspiciousKeyName = /api_?key|secret|token|password|auth_?token|private_?key/i.test(variableName);
257
- const entropy = this.calculateShannonEntropy(value);
258
-
259
- if ((isSuspiciousKeyName && value.length > 8) || (entropy > this.entropyThreshold && value.length > 16)) {
260
- fileNode.securityThreats.push({
261
- identifier: variableName,
262
- entropy: parseFloat(entropy.toFixed(2)),
263
- position: initializer.getStart(sourceFile),
264
- riskCode: 'HIGH_RISK_SECRET_LEAK'
265
- });
266
- }
243
+ hasExportModifier(node) {
244
+ return node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
267
245
  }
268
246
 
269
- calculateShannonEntropy(str) {
270
- const map = {};
271
- for (let i = 0; i < str.length; i++) {
272
- const char = str[i];
273
- map[char] = (map[char] || 0) + 1;
274
- }
275
- let entropy = 0;
276
- for (const char in map) {
277
- const p = map[char] / str.length;
278
- entropy -= p * Math.log2(p);
279
- }
280
- return entropy;
247
+ isNodeDeclarationName(node) {
248
+ const parent = node.parent;
249
+ if (!parent) return false;
250
+ return (ts.isVariableDeclaration(parent) || ts.isFunctionDeclaration(parent) ||
251
+ ts.isClassDeclaration(parent) || ts.isInterfaceDeclaration(parent) ||
252
+ ts.isEnumDeclaration(parent) || ts.isModuleDeclaration(parent)) && parent.name === node;
281
253
  }
282
254
 
283
- /**
284
- * Challenge #18 & #8: Parse JSDoc suppression blocks right out of code statements.
285
- */
286
255
  extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
287
256
  const fullText = sourceFile.text;
288
- // Scan all comments in the file for @scaffold-suppress
289
257
  const commentRegex = /\/\*\*?[\s\S]*?\*\/|\/\/.*/g;
290
258
  let match;
291
259
  while ((match = commentRegex.exec(fullText)) !== null) {
292
- const comment = match[0];
293
- const suppressMatches = comment.match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
260
+ const suppressMatches = match[0].match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
294
261
  if (suppressMatches) {
295
- suppressMatches.forEach(m => {
296
- const directive = m.replace('@scaffold-suppress', '').trim();
297
- fileNode.localSuppressedRules.add(directive);
298
- });
262
+ suppressMatches.forEach(m => fileNode.localSuppressedRules.add(m.replace('@scaffold-suppress', '').trim()));
299
263
  }
300
264
  }
301
265
  }
302
-
303
- hasExportModifier(node) {
304
- if (!node || !node.modifiers) return false;
305
- return node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword);
306
- }
307
-
308
- isNodeDeclarationName(node) {
309
- const parent = node.parent;
310
- if (!parent) return false;
311
- if (ts.isVariableDeclaration(parent) && parent.name === node) return true;
312
- if (ts.isFunctionDeclaration(parent) && parent.name === node) return true;
313
- if (ts.isClassDeclaration(parent) && parent.name === node) return true;
314
- if (ts.isInterfaceDeclaration(parent) && parent.name === node) return true;
315
- if (ts.isImportSpecifier(parent) && parent.name === node) return true;
316
- return false;
317
- }
318
-
319
- getScriptKind(filePath) {
320
- if (filePath.endsWith('.ts')) return ts.ScriptKind.TS;
321
- if (filePath.endsWith('.tsx')) return ts.ScriptKind.TSX;
322
- if (filePath.endsWith('.jsx')) return ts.ScriptKind.JSX;
323
- return ts.ScriptKind.JS;
324
- }
325
266
  }