doc-freshness-checker 2.0.23 → 2.2.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 +19 -0
- package/dist/config/defaults.js +9 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/manifests/manifestInventory.d.ts +28 -0
- package/dist/manifests/manifestInventory.js +225 -0
- package/dist/manifests/manifestInventory.js.map +1 -0
- package/dist/manifests/manifestInventory.test.d.ts +1 -0
- package/dist/manifests/manifestInventory.test.js +176 -0
- package/dist/manifests/manifestInventory.test.js.map +1 -0
- package/dist/manifests/validatorConstructors.test.d.ts +1 -0
- package/dist/manifests/validatorConstructors.test.js +17 -0
- package/dist/manifests/validatorConstructors.test.js.map +1 -0
- package/dist/parsers/documentParser.d.ts +2 -3
- package/dist/parsers/documentParser.js.map +1 -1
- package/dist/parsers/extractorInterface.test.d.ts +1 -0
- package/dist/parsers/extractorInterface.test.js +34 -0
- package/dist/parsers/extractorInterface.test.js.map +1 -0
- package/dist/parsers/extractors/codeSnippetExtractor.js.map +1 -1
- package/dist/plugins/plugin.d.ts +5 -1
- package/dist/plugins/plugin.js +5 -1
- package/dist/plugins/plugin.js.map +1 -1
- package/dist/reporters/consoleReporter.d.ts +5 -11
- package/dist/reporters/consoleReporter.js +74 -76
- package/dist/reporters/consoleReporter.js.map +1 -1
- package/dist/reporters/consoleReporter.test.js +22 -27
- package/dist/reporters/consoleReporter.test.js.map +1 -1
- package/dist/reporters/enhancedReporter.d.ts +51 -7
- package/dist/reporters/enhancedReporter.js +128 -74
- package/dist/reporters/enhancedReporter.js.map +1 -1
- package/dist/reporters/enhancedReporter.test.js +110 -122
- package/dist/reporters/enhancedReporter.test.js.map +1 -1
- package/dist/reporters/jsonReporter.d.ts +6 -6
- package/dist/reporters/jsonReporter.js +24 -14
- package/dist/reporters/jsonReporter.js.map +1 -1
- package/dist/reporters/jsonReporter.test.js +26 -11
- package/dist/reporters/jsonReporter.test.js.map +1 -1
- package/dist/reporters/markdownIssueCells.d.ts +7 -0
- package/dist/reporters/markdownIssueCells.js +9 -0
- package/dist/reporters/markdownIssueCells.js.map +1 -0
- package/dist/reporters/markdownReporter.d.ts +2 -7
- package/dist/reporters/markdownReporter.js +57 -56
- package/dist/reporters/markdownReporter.js.map +1 -1
- package/dist/reporters/markdownReporter.test.js +104 -76
- package/dist/reporters/markdownReporter.test.js.map +1 -1
- package/dist/reporters/publicReporterApi.test.d.ts +1 -0
- package/dist/reporters/publicReporterApi.test.js +28 -0
- package/dist/reporters/publicReporterApi.test.js.map +1 -0
- package/dist/reporters/reportContext.d.ts +12 -0
- package/dist/reporters/reportContext.js +8 -0
- package/dist/reporters/reportContext.js.map +1 -0
- package/dist/reporters/reportContext.test.d.ts +1 -0
- package/dist/reporters/reportContext.test.js +30 -0
- package/dist/reporters/reportContext.test.js.map +1 -0
- package/dist/reporters/reporters.characterization.test.d.ts +1 -0
- package/dist/reporters/reporters.characterization.test.js +208 -0
- package/dist/reporters/reporters.characterization.test.js.map +1 -0
- package/dist/runner.js +67 -63
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +444 -42
- package/dist/runner.test.js.map +1 -1
- package/dist/semantic/vectorSearch.js +2 -1
- package/dist/semantic/vectorSearch.js.map +1 -1
- package/dist/semantic/vectorSearch.test.js +2 -2
- package/dist/semantic/vectorSearch.test.js.map +1 -1
- package/dist/source/sourceIndex.d.ts +39 -0
- package/dist/source/sourceIndex.js +444 -0
- package/dist/source/sourceIndex.js.map +1 -0
- package/dist/source/sourceIndex.largeGlob.test.d.ts +1 -0
- package/dist/source/sourceIndex.largeGlob.test.js +29 -0
- package/dist/source/sourceIndex.largeGlob.test.js.map +1 -0
- package/dist/source/sourceIndex.test.d.ts +1 -0
- package/dist/source/sourceIndex.test.js +263 -0
- package/dist/source/sourceIndex.test.js.map +1 -0
- package/dist/source/sourceValidators.d.ts +7 -0
- package/dist/source/sourceValidators.js +8 -0
- package/dist/source/sourceValidators.js.map +1 -0
- package/dist/source/sourceValidators.test.d.ts +1 -0
- package/dist/source/sourceValidators.test.js +17 -0
- package/dist/source/sourceValidators.test.js.map +1 -0
- package/dist/types.d.ts +34 -2
- package/dist/validators/codePatternValidator.d.ts +5 -11
- package/dist/validators/codePatternValidator.js +17 -145
- package/dist/validators/codePatternValidator.js.map +1 -1
- package/dist/validators/codeSnippetValidator.d.ts +5 -18
- package/dist/validators/codeSnippetValidator.js +19 -312
- package/dist/validators/codeSnippetValidator.js.map +1 -1
- package/dist/validators/dependencyValidator.d.ts +0 -5
- package/dist/validators/dependencyValidator.js +7 -64
- package/dist/validators/dependencyValidator.js.map +1 -1
- package/dist/validators/versionValidator.d.ts +1 -10
- package/dist/validators/versionValidator.js +7 -157
- package/dist/validators/versionValidator.js.map +1 -1
- package/dist/validators/versionValidator.test.js +1 -101
- package/dist/validators/versionValidator.test.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,175 +1,47 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { glob } from 'glob';
|
|
4
1
|
import { findSimilar } from '../utils/similarity.js';
|
|
5
|
-
|
|
6
|
-
* Language-specific source file patterns and code symbol extractors
|
|
7
|
-
*/
|
|
8
|
-
const languageConfigs = {
|
|
9
|
-
javascript: {
|
|
10
|
-
extensions: ['js', 'jsx', 'mjs', 'cjs'],
|
|
11
|
-
patterns: [
|
|
12
|
-
{ regex: /\bclass\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'class' },
|
|
13
|
-
{ regex: /\bexport\s+(?:async\s+)?function\s+([a-zA-Z][a-zA-Z0-9]+)/g, kind: 'function' },
|
|
14
|
-
{ regex: /\bexport\s+const\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'const' },
|
|
15
|
-
{ regex: /\bfunction\s+([a-zA-Z][a-zA-Z0-9]+)/g, kind: 'function' },
|
|
16
|
-
],
|
|
17
|
-
},
|
|
18
|
-
typescript: {
|
|
19
|
-
extensions: ['ts', 'tsx'],
|
|
20
|
-
patterns: [
|
|
21
|
-
{ regex: /\bclass\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'class' },
|
|
22
|
-
{ regex: /\binterface\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'interface' },
|
|
23
|
-
{ regex: /\btype\s+([A-Z][a-zA-Z0-9]+)\s*=/g, kind: 'type' },
|
|
24
|
-
{ regex: /\bexport\s+(?:async\s+)?function\s+([a-zA-Z][a-zA-Z0-9]+)/g, kind: 'function' },
|
|
25
|
-
{ regex: /\bexport\s+const\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'const' },
|
|
26
|
-
{ regex: /\bfunction\s+([a-zA-Z][a-zA-Z0-9]+)/g, kind: 'function' },
|
|
27
|
-
],
|
|
28
|
-
},
|
|
29
|
-
python: {
|
|
30
|
-
extensions: ['py'],
|
|
31
|
-
patterns: [
|
|
32
|
-
{ regex: /\bclass\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'class' },
|
|
33
|
-
{ regex: /\bdef\s+([a-z_][a-zA-Z0-9_]*)/g, kind: 'function' },
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
go: {
|
|
37
|
-
extensions: ['go'],
|
|
38
|
-
patterns: [
|
|
39
|
-
{ regex: /\btype\s+([A-Z][a-zA-Z0-9]+)\s+struct/g, kind: 'struct' },
|
|
40
|
-
{ regex: /\btype\s+([A-Z][a-zA-Z0-9]+)\s+interface/g, kind: 'interface' },
|
|
41
|
-
{ regex: /\bfunc\s+(?:\([^)]+\)\s+)?([A-Z][a-zA-Z0-9]+)/g, kind: 'function' },
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
rust: {
|
|
45
|
-
extensions: ['rs'],
|
|
46
|
-
patterns: [
|
|
47
|
-
{ regex: /\bstruct\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'struct' },
|
|
48
|
-
{ regex: /\benum\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'enum' },
|
|
49
|
-
{ regex: /\btrait\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'trait' },
|
|
50
|
-
{ regex: /\bpub\s+fn\s+([a-z_][a-zA-Z0-9_]*)/g, kind: 'function' },
|
|
51
|
-
{ regex: /\bfn\s+([a-z_][a-zA-Z0-9_]*)/g, kind: 'function' },
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
java: {
|
|
55
|
-
extensions: ['java'],
|
|
56
|
-
patterns: [
|
|
57
|
-
{ regex: /\bclass\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'class' },
|
|
58
|
-
{ regex: /\binterface\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'interface' },
|
|
59
|
-
{ regex: /\benum\s+([A-Z][a-zA-Z0-9]+)/g, kind: 'enum' },
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
};
|
|
2
|
+
import { SourceIndex, languageConfigs as sourceLanguageConfigs } from '../source/sourceIndex.js';
|
|
63
3
|
import { isIllustrativeSymbol } from '../utils/illustrativePatterns.js';
|
|
64
4
|
import { createIllustrativeSkippedResult, getRuleSeverity, severityForIllustrative } from '../utils/validation.js';
|
|
5
|
+
const languageConfigs = sourceLanguageConfigs;
|
|
65
6
|
/**
|
|
66
7
|
* Validates code patterns exist in source files
|
|
67
8
|
*/
|
|
68
9
|
export class CodePatternValidator {
|
|
10
|
+
index;
|
|
69
11
|
/** @internal */
|
|
70
12
|
incrementalCaptureScope = 'project';
|
|
71
13
|
/** @internal */
|
|
72
14
|
incrementalInputsRequiredForGraph = true;
|
|
73
15
|
sourceIndex;
|
|
74
16
|
sourceFiles; // Stores file content for vector search
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
constructor() {
|
|
17
|
+
incrementalInputs;
|
|
18
|
+
constructor(index = new SourceIndex()) {
|
|
19
|
+
this.index = index;
|
|
79
20
|
this.sourceIndex = null;
|
|
80
21
|
this.sourceFiles = null;
|
|
81
|
-
this.
|
|
82
|
-
this.sourceInputsAvailable = false;
|
|
83
|
-
this.capturedInputs = null;
|
|
22
|
+
this.incrementalInputs = null;
|
|
84
23
|
}
|
|
85
24
|
async buildSourceIndex(config) {
|
|
86
25
|
if (this.sourceIndex) {
|
|
87
26
|
return;
|
|
88
27
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.sourceInputs = new Map();
|
|
92
|
-
this.sourceInputsAvailable = true;
|
|
93
|
-
// Use configured source patterns or detect automatically
|
|
94
|
-
const sourcePatterns = config.sourcePatterns || this.detectSourcePatterns();
|
|
95
|
-
for (const pattern of sourcePatterns) {
|
|
96
|
-
try {
|
|
97
|
-
const files = await glob(pattern, {
|
|
98
|
-
cwd: config.rootDir,
|
|
99
|
-
absolute: true,
|
|
100
|
-
nodir: true,
|
|
101
|
-
ignore: ['**/*.test.*', '**/*.spec.*', '**/*.d.ts', '**/node_modules/**', '**/vendor/**'],
|
|
102
|
-
});
|
|
103
|
-
for (const file of files) {
|
|
104
|
-
const absolutePath = path.resolve(file);
|
|
105
|
-
if (this.sourceInputs.has(absolutePath)) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
const content = await fs.promises.readFile(absolutePath, 'utf-8');
|
|
110
|
-
const relativePath = path.relative(config.rootDir || process.cwd(), absolutePath);
|
|
111
|
-
const lang = this.detectLanguage(absolutePath);
|
|
112
|
-
this.indexContent(content, relativePath, lang);
|
|
113
|
-
// Store file content for vector search
|
|
114
|
-
this.sourceFiles.set(relativePath, { content, language: lang });
|
|
115
|
-
this.sourceInputs.set(absolutePath, content);
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// Skip unreadable files
|
|
119
|
-
this.sourceInputsAvailable = false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// Skip invalid patterns
|
|
125
|
-
this.sourceInputsAvailable = false;
|
|
126
|
-
}
|
|
28
|
+
try {
|
|
29
|
+
this.useSnapshot(await this.index.load(config, 'pattern'));
|
|
127
30
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
detectSourcePatterns() {
|
|
133
|
-
// Auto-detect based on what files exist
|
|
134
|
-
const patterns = [];
|
|
135
|
-
for (const conf of Object.values(languageConfigs)) {
|
|
136
|
-
for (const ext of conf.extensions) {
|
|
137
|
-
patterns.push(`**/*.${ext}`);
|
|
138
|
-
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
this.useSnapshot(await this.index.load(config, 'pattern'));
|
|
33
|
+
throw error;
|
|
139
34
|
}
|
|
140
|
-
return patterns;
|
|
141
35
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return lang;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return 'javascript';
|
|
36
|
+
useSnapshot(snapshot) {
|
|
37
|
+
this.sourceIndex = snapshot.symbols;
|
|
38
|
+
this.sourceFiles = snapshot.patternFiles;
|
|
39
|
+
this.incrementalInputs = snapshot.patternInputs;
|
|
150
40
|
}
|
|
151
41
|
/** @internal */
|
|
152
42
|
async getIncrementalInputs(_references, _document, config) {
|
|
153
43
|
await this.buildSourceIndex(config);
|
|
154
|
-
return this.
|
|
155
|
-
}
|
|
156
|
-
indexContent(content, filePath, language) {
|
|
157
|
-
const langConfig = languageConfigs[language];
|
|
158
|
-
if (!langConfig) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
for (const { regex, kind } of langConfig.patterns) {
|
|
162
|
-
let match;
|
|
163
|
-
// Clone regex to avoid state issues
|
|
164
|
-
const re = new RegExp(regex.source, regex.flags);
|
|
165
|
-
while ((match = re.exec(content)) !== null) {
|
|
166
|
-
const name = match[1];
|
|
167
|
-
if (!this.sourceIndex.has(name)) {
|
|
168
|
-
this.sourceIndex.set(name, []);
|
|
169
|
-
}
|
|
170
|
-
this.sourceIndex.get(name).push({ filePath, kind, language });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
44
|
+
return this.incrementalInputs;
|
|
173
45
|
}
|
|
174
46
|
async validateBatch(references, _document, config) {
|
|
175
47
|
await this.buildSourceIndex(config);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codePatternValidator.js","sourceRoot":"","sources":["../../src/validators/codePatternValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"codePatternValidator.js","sourceRoot":"","sources":["../../src/validators/codePatternValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import type { DocFreshnessConfig, Document, Reference, ValidationResult } from '../types.js';
|
|
2
|
-
interface FunctionSignature {
|
|
3
|
-
params: string[];
|
|
4
|
-
requiredParams: number;
|
|
5
|
-
filePath: string;
|
|
6
|
-
}
|
|
1
|
+
import type { DocFreshnessConfig, Document, FunctionSignature, Reference, ValidationResult } from '../types.js';
|
|
7
2
|
/**
|
|
8
3
|
* Validates code-snippet references extracted from documentation code blocks.
|
|
9
4
|
*
|
|
@@ -13,15 +8,17 @@ interface FunctionSignature {
|
|
|
13
8
|
* 3. Config keys — keys exist in the referenced type/interface
|
|
14
9
|
*/
|
|
15
10
|
export declare class CodeSnippetValidator {
|
|
11
|
+
private readonly index;
|
|
16
12
|
private sourceFiles;
|
|
17
13
|
private functionSignatures;
|
|
18
14
|
private interfaceKeys;
|
|
15
|
+
private exportsByFile;
|
|
19
16
|
private customPatterns;
|
|
20
|
-
|
|
17
|
+
constructor();
|
|
21
18
|
validateBatch(references: Reference[], _document: Document, config: DocFreshnessConfig): Promise<ValidationResult[]>;
|
|
22
19
|
private initCustomPatterns;
|
|
23
20
|
private buildIndex;
|
|
24
|
-
private
|
|
21
|
+
private useSnapshot;
|
|
25
22
|
private validateImport;
|
|
26
23
|
private resolveImportPath;
|
|
27
24
|
private resolvePythonImportPath;
|
|
@@ -29,20 +26,10 @@ export declare class CodeSnippetValidator {
|
|
|
29
26
|
private getExportedSymbols;
|
|
30
27
|
private validateFunctionCall;
|
|
31
28
|
private validateConfigKeys;
|
|
32
|
-
private indexFunctionSignatures;
|
|
33
|
-
private addSignature;
|
|
34
|
-
private parseParameters;
|
|
35
|
-
private extractParamName;
|
|
36
|
-
private isOptionalParam;
|
|
37
29
|
private legacyImportSpecifiers;
|
|
38
30
|
private extractSpecifiersByPrefix;
|
|
39
31
|
private validateFunctionParameterNames;
|
|
40
32
|
private parameterNamesMatch;
|
|
41
|
-
private indexInterfaceDefinitions;
|
|
42
|
-
private addInterfaceKeys;
|
|
43
|
-
private extractBraceContent;
|
|
44
|
-
private extractPropertyKeys;
|
|
45
33
|
getFunctionSignatures(): Map<string, FunctionSignature[]> | null;
|
|
46
34
|
getInterfaceKeys(): Map<string, Set<string>> | null;
|
|
47
35
|
}
|
|
48
|
-
export {};
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { SourceIndex } from '../source/sourceIndex.js';
|
|
4
4
|
import { isIllustrativePath, isIllustrativeSymbol, compilePatterns } from '../utils/illustrativePatterns.js';
|
|
5
5
|
import { findSimilar } from '../utils/similarity.js';
|
|
6
6
|
import { createIllustrativeSkippedResult, getRuleSeverity, severityForIllustrative } from '../utils/validation.js';
|
|
7
|
-
const LANG_EXTENSIONS = {
|
|
8
|
-
ts: 'typescript',
|
|
9
|
-
tsx: 'typescript',
|
|
10
|
-
js: 'javascript',
|
|
11
|
-
jsx: 'javascript',
|
|
12
|
-
mjs: 'javascript',
|
|
13
|
-
cjs: 'javascript',
|
|
14
|
-
py: 'python',
|
|
15
|
-
go: 'go',
|
|
16
|
-
};
|
|
17
7
|
/**
|
|
18
8
|
* Validates code-snippet references extracted from documentation code blocks.
|
|
19
9
|
*
|
|
@@ -23,11 +13,15 @@ const LANG_EXTENSIONS = {
|
|
|
23
13
|
* 3. Config keys — keys exist in the referenced type/interface
|
|
24
14
|
*/
|
|
25
15
|
export class CodeSnippetValidator {
|
|
16
|
+
index;
|
|
26
17
|
sourceFiles = null;
|
|
27
18
|
functionSignatures = null;
|
|
28
19
|
interfaceKeys = null;
|
|
20
|
+
exportsByFile = null;
|
|
29
21
|
customPatterns = [];
|
|
30
|
-
|
|
22
|
+
constructor(index = new SourceIndex()) {
|
|
23
|
+
this.index = index;
|
|
24
|
+
}
|
|
31
25
|
async validateBatch(references, _document, config) {
|
|
32
26
|
await this.buildIndex(config);
|
|
33
27
|
const ruleConfig = config.rules?.['code-snippet'];
|
|
@@ -59,44 +53,22 @@ export class CodeSnippetValidator {
|
|
|
59
53
|
// Index building
|
|
60
54
|
// ---------------------------------------------------------------------------
|
|
61
55
|
async buildIndex(config) {
|
|
62
|
-
if (this.
|
|
56
|
+
if (this.sourceFiles) {
|
|
63
57
|
return;
|
|
64
58
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
for (const pattern of sourcePatterns) {
|
|
72
|
-
try {
|
|
73
|
-
const files = await glob(pattern, {
|
|
74
|
-
cwd: rootDir,
|
|
75
|
-
absolute: true,
|
|
76
|
-
ignore: ['**/*.test.*', '**/*.spec.*', '**/*.d.ts', '**/node_modules/**', '**/vendor/**', '**/dist/**', '**/build/**'],
|
|
77
|
-
});
|
|
78
|
-
for (const file of files) {
|
|
79
|
-
try {
|
|
80
|
-
const content = await fs.promises.readFile(file, 'utf-8');
|
|
81
|
-
const relativePath = path.relative(rootDir, file);
|
|
82
|
-
const lang = this.detectLanguage(file);
|
|
83
|
-
this.sourceFiles.set(relativePath, { content, language: lang });
|
|
84
|
-
this.indexFunctionSignatures(content, relativePath, lang);
|
|
85
|
-
this.indexInterfaceDefinitions(content, relativePath, lang);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
/* skip unreadable files */
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
/* skip invalid patterns */
|
|
94
|
-
}
|
|
59
|
+
try {
|
|
60
|
+
this.useSnapshot(await this.index.load(config, 'snippet'));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
this.useSnapshot(await this.index.load(config, 'snippet'));
|
|
64
|
+
throw error;
|
|
95
65
|
}
|
|
96
66
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
67
|
+
useSnapshot(snapshot) {
|
|
68
|
+
this.sourceFiles = snapshot.snippetFiles;
|
|
69
|
+
this.functionSignatures = snapshot.functionSignatures;
|
|
70
|
+
this.interfaceKeys = snapshot.interfaceKeys;
|
|
71
|
+
this.exportsByFile = snapshot.exportsByFile;
|
|
100
72
|
}
|
|
101
73
|
// ---------------------------------------------------------------------------
|
|
102
74
|
// Import validation
|
|
@@ -228,77 +200,7 @@ export class CodeSnippetValidator {
|
|
|
228
200
|
return null;
|
|
229
201
|
}
|
|
230
202
|
getExportedSymbols(filePath) {
|
|
231
|
-
|
|
232
|
-
if (!fileData) {
|
|
233
|
-
return new Set();
|
|
234
|
-
}
|
|
235
|
-
const exports = new Set();
|
|
236
|
-
const content = fileData.content;
|
|
237
|
-
if (fileData.language === 'python') {
|
|
238
|
-
const defPattern = /^(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/gm;
|
|
239
|
-
let pythonMatch;
|
|
240
|
-
while ((pythonMatch = defPattern.exec(content)) !== null) {
|
|
241
|
-
if (!pythonMatch[1].startsWith('_')) {
|
|
242
|
-
exports.add(pythonMatch[1]);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
const classPattern = /^class\s+([A-Za-z_]\w*)\b/gm;
|
|
246
|
-
while ((pythonMatch = classPattern.exec(content)) !== null) {
|
|
247
|
-
if (!pythonMatch[1].startsWith('_')) {
|
|
248
|
-
exports.add(pythonMatch[1]);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const assignmentPattern = /^([A-Za-z_]\w*)\s*=/gm;
|
|
252
|
-
while ((pythonMatch = assignmentPattern.exec(content)) !== null) {
|
|
253
|
-
if (!pythonMatch[1].startsWith('_')) {
|
|
254
|
-
exports.add(pythonMatch[1]);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return exports;
|
|
258
|
-
}
|
|
259
|
-
// export [async] function|class|const|let|var|interface|type|enum Name
|
|
260
|
-
const directPattern = /export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+(\w+)/g;
|
|
261
|
-
let match;
|
|
262
|
-
while ((match = directPattern.exec(content)) !== null) {
|
|
263
|
-
exports.add(match[1]);
|
|
264
|
-
}
|
|
265
|
-
// export { Name1, Name2 as Alias }
|
|
266
|
-
const namedPattern = /export\s*\{([^}]+)\}/g;
|
|
267
|
-
while ((match = namedPattern.exec(content)) !== null) {
|
|
268
|
-
for (const item of match[1].split(',')) {
|
|
269
|
-
const original = item
|
|
270
|
-
.trim()
|
|
271
|
-
.split(/\s+as\s+/)[0]
|
|
272
|
-
.trim();
|
|
273
|
-
if (original) {
|
|
274
|
-
exports.add(original);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// export default identifier;
|
|
279
|
-
const defaultPattern = /export\s+default\s+(\w+)\s*[;\n]/g;
|
|
280
|
-
while ((match = defaultPattern.exec(content)) !== null) {
|
|
281
|
-
exports.add(match[1]);
|
|
282
|
-
exports.add('default');
|
|
283
|
-
}
|
|
284
|
-
const defaultDeclarationPattern = /export\s+default\s+(?:async\s+)?(?:function|class)\b(?:\s+(\w+))?/g;
|
|
285
|
-
while ((match = defaultDeclarationPattern.exec(content)) !== null) {
|
|
286
|
-
if (match[1]) {
|
|
287
|
-
exports.add(match[1]);
|
|
288
|
-
}
|
|
289
|
-
exports.add('default');
|
|
290
|
-
}
|
|
291
|
-
// module.exports = { name1, name2: value }
|
|
292
|
-
const cjsPattern = /module\.exports\s*=\s*\{([^}]+)\}/g;
|
|
293
|
-
while ((match = cjsPattern.exec(content)) !== null) {
|
|
294
|
-
for (const item of match[1].split(',')) {
|
|
295
|
-
const name = item.trim().split(/\s*:/)[0].trim();
|
|
296
|
-
if (name) {
|
|
297
|
-
exports.add(name);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return exports;
|
|
203
|
+
return this.exportsByFile?.get(filePath) || new Set();
|
|
302
204
|
}
|
|
303
205
|
// ---------------------------------------------------------------------------
|
|
304
206
|
// Function call validation
|
|
@@ -393,129 +295,6 @@ export class CodeSnippetValidator {
|
|
|
393
295
|
suggestion: suggestions.some((s) => s.includes('→')) ? `Did you mean: ${suggestions.join(', ')}?` : null,
|
|
394
296
|
};
|
|
395
297
|
}
|
|
396
|
-
// ---------------------------------------------------------------------------
|
|
397
|
-
// Signature & interface indexing
|
|
398
|
-
// ---------------------------------------------------------------------------
|
|
399
|
-
indexFunctionSignatures(content, filePath, lang) {
|
|
400
|
-
if (lang === 'javascript' || lang === 'typescript') {
|
|
401
|
-
const funcPattern = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
|
|
402
|
-
let match;
|
|
403
|
-
while ((match = funcPattern.exec(content)) !== null) {
|
|
404
|
-
this.addSignature(match[1], match[2], filePath, lang);
|
|
405
|
-
}
|
|
406
|
-
// Arrow / const functions
|
|
407
|
-
const arrowPattern = /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)[^{;]*=>/g;
|
|
408
|
-
while ((match = arrowPattern.exec(content)) !== null) {
|
|
409
|
-
this.addSignature(match[1], match[2], filePath, lang);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
if (lang === 'python') {
|
|
413
|
-
const defPattern = /def\s+(\w+)\s*\(([^)]*)\)/g;
|
|
414
|
-
let match;
|
|
415
|
-
while ((match = defPattern.exec(content)) !== null) {
|
|
416
|
-
this.addSignature(match[1], match[2], filePath, lang);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
addSignature(name, paramsStr, filePath, lang) {
|
|
421
|
-
const { names, requiredCount } = this.parseParameters(paramsStr, lang);
|
|
422
|
-
let params = names;
|
|
423
|
-
let required = requiredCount;
|
|
424
|
-
// Strip Python self/cls
|
|
425
|
-
if (lang === 'python' && params.length > 0 && (params[0] === 'self' || params[0] === 'cls')) {
|
|
426
|
-
params = params.slice(1);
|
|
427
|
-
required = Math.max(0, required - 1);
|
|
428
|
-
}
|
|
429
|
-
const signatures = this.functionSignatures.get(name) || [];
|
|
430
|
-
if (signatures.length === 0) {
|
|
431
|
-
this.functionSignatures.set(name, signatures);
|
|
432
|
-
}
|
|
433
|
-
signatures.push({ params, requiredParams: required, filePath });
|
|
434
|
-
}
|
|
435
|
-
parseParameters(paramsStr, lang) {
|
|
436
|
-
if (!paramsStr.trim()) {
|
|
437
|
-
return { names: [], requiredCount: 0 };
|
|
438
|
-
}
|
|
439
|
-
const rawParams = [];
|
|
440
|
-
let depth = 0;
|
|
441
|
-
let current = '';
|
|
442
|
-
for (const ch of paramsStr) {
|
|
443
|
-
if ('(<[{'.includes(ch)) {
|
|
444
|
-
depth++;
|
|
445
|
-
}
|
|
446
|
-
else if (')>]}'.includes(ch)) {
|
|
447
|
-
depth--;
|
|
448
|
-
}
|
|
449
|
-
if (ch === ',' && depth === 0) {
|
|
450
|
-
rawParams.push(current.trim());
|
|
451
|
-
current = '';
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
current += ch;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
if (current.trim()) {
|
|
458
|
-
rawParams.push(current.trim());
|
|
459
|
-
}
|
|
460
|
-
const names = [];
|
|
461
|
-
let requiredCount = 0;
|
|
462
|
-
let seenOptional = false;
|
|
463
|
-
for (const raw of rawParams) {
|
|
464
|
-
const name = this.extractParamName(raw, lang);
|
|
465
|
-
if (!name) {
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
names.push(name);
|
|
469
|
-
const isOptional = this.isOptionalParam(raw, lang);
|
|
470
|
-
const isRest = raw.trim().startsWith('...') || raw.trim().startsWith('*');
|
|
471
|
-
if (!isOptional && !isRest && !seenOptional) {
|
|
472
|
-
requiredCount++;
|
|
473
|
-
}
|
|
474
|
-
else {
|
|
475
|
-
seenOptional = true;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return { names, requiredCount };
|
|
479
|
-
}
|
|
480
|
-
extractParamName(raw, lang) {
|
|
481
|
-
if (!raw) {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
if (lang === 'typescript' || lang === 'javascript') {
|
|
485
|
-
const match = raw.match(/^(?:\.\.\.)?(\w+)/);
|
|
486
|
-
return match ? match[1] : null;
|
|
487
|
-
}
|
|
488
|
-
if (lang === 'python') {
|
|
489
|
-
const match = raw.match(/^\*{0,2}(\w+)/);
|
|
490
|
-
return match ? match[1] : null;
|
|
491
|
-
}
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
isOptionalParam(raw, lang) {
|
|
495
|
-
if (raw.trim().startsWith('...') || raw.trim().startsWith('*')) {
|
|
496
|
-
return true;
|
|
497
|
-
}
|
|
498
|
-
if (lang === 'typescript' || lang === 'javascript') {
|
|
499
|
-
// name? (TS optional marker right after identifier)
|
|
500
|
-
if (/^\w+\s*\?/.test(raw.trim())) {
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
// Default value at top level (= but not =>)
|
|
505
|
-
let depth = 0;
|
|
506
|
-
for (let i = 0; i < raw.length; i++) {
|
|
507
|
-
if ('({[<'.includes(raw[i])) {
|
|
508
|
-
depth++;
|
|
509
|
-
}
|
|
510
|
-
else if (')}]>'.includes(raw[i])) {
|
|
511
|
-
depth--;
|
|
512
|
-
}
|
|
513
|
-
if (depth === 0 && raw[i] === '=' && raw[i + 1] !== '>') {
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
298
|
legacyImportSpecifiers(linkText) {
|
|
520
299
|
return linkText
|
|
521
300
|
? linkText
|
|
@@ -560,78 +339,6 @@ export class CodeSnippetValidator {
|
|
|
560
339
|
}
|
|
561
340
|
return argumentNames.every((name, index) => name === parameterNames[index]);
|
|
562
341
|
}
|
|
563
|
-
indexInterfaceDefinitions(content, _filePath, lang) {
|
|
564
|
-
if (lang !== 'typescript') {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
// interface Name { ... }
|
|
568
|
-
const ifacePattern = /(?:export\s+)?interface\s+(\w+)(?:\s+extends\s+[\w,\s<>]+)?\s*\{/g;
|
|
569
|
-
let match;
|
|
570
|
-
while ((match = ifacePattern.exec(content)) !== null) {
|
|
571
|
-
this.addInterfaceKeys(match[1], content, match.index + match[0].length - 1);
|
|
572
|
-
}
|
|
573
|
-
// type Name = { ... }
|
|
574
|
-
const typeObjPattern = /(?:export\s+)?type\s+(\w+)\s*=\s*\{/g;
|
|
575
|
-
while ((match = typeObjPattern.exec(content)) !== null) {
|
|
576
|
-
this.addInterfaceKeys(match[1], content, match.index + match[0].length - 1);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
addInterfaceKeys(name, content, braceStart) {
|
|
580
|
-
const body = this.extractBraceContent(content, braceStart);
|
|
581
|
-
if (!body) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
const keys = this.extractPropertyKeys(body);
|
|
585
|
-
if (keys.size === 0) {
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
if (!this.interfaceKeys.has(name)) {
|
|
589
|
-
this.interfaceKeys.set(name, new Set());
|
|
590
|
-
}
|
|
591
|
-
for (const key of keys) {
|
|
592
|
-
this.interfaceKeys.get(name).add(key);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
extractBraceContent(content, braceStart) {
|
|
596
|
-
let depth = 0;
|
|
597
|
-
for (let i = braceStart; i < content.length; i++) {
|
|
598
|
-
if (content[i] === '{') {
|
|
599
|
-
depth++;
|
|
600
|
-
}
|
|
601
|
-
else if (content[i] === '}') {
|
|
602
|
-
depth--;
|
|
603
|
-
if (depth === 0) {
|
|
604
|
-
return content.substring(braceStart + 1, i);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
extractPropertyKeys(body) {
|
|
611
|
-
const keys = new Set();
|
|
612
|
-
let depth = 0;
|
|
613
|
-
for (const line of body.split('\n')) {
|
|
614
|
-
const trimmed = line.trim();
|
|
615
|
-
if (!trimmed) {
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
if (depth === 0) {
|
|
619
|
-
const keyMatch = trimmed.match(/^(?:readonly\s+)?(\w+)\??\s*:/);
|
|
620
|
-
if (keyMatch) {
|
|
621
|
-
keys.add(keyMatch[1]);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
for (const ch of trimmed) {
|
|
625
|
-
if (ch === '{') {
|
|
626
|
-
depth++;
|
|
627
|
-
}
|
|
628
|
-
else if (ch === '}') {
|
|
629
|
-
depth--;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return keys;
|
|
634
|
-
}
|
|
635
342
|
// Exposed for testing / integration
|
|
636
343
|
getFunctionSignatures() {
|
|
637
344
|
return this.functionSignatures;
|