@dailephd/my-dev-kit-lab 0.3.1 → 0.3.3
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 +24 -18
- package/dist/scripts/audits/runAudit.js +8 -2
- package/dist/scripts/security/validate.js +16 -47
- package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +191 -1
- package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +202 -4
- package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +42 -9
- package/dist/src/audits/codeRot/detectors/testRotDetector.js +241 -1
- package/dist/src/audits/codeRot/utils/filePatternUtils.js +19 -1
- package/dist/src/audits/codeRot/utils/jvmSourceFactsUtils.js +153 -0
- package/dist/src/audits/core/auditConfig.js +2 -2
- package/dist/src/audits/core/auditRunner.js +19 -0
- package/dist/src/audits/core/auditTypes.js +14 -7
- package/dist/src/audits/core/javaAnalyzer.js +282 -0
- package/dist/src/audits/core/jvmProjectMetadata.js +106 -0
- package/dist/src/audits/core/kotlinAnalyzer.js +280 -0
- package/dist/src/audits/core/languageAnalyzerRegistry.js +12 -3
- package/dist/src/audits/core/projectInventory.js +76 -3
- package/dist/src/audits/core/pythonAnalyzer.js +328 -0
- package/dist/src/audits/core/pythonProjectMetadata.js +80 -0
- package/dist/src/audits/report/auditReportModel.js +3 -0
- package/dist/src/audits/report/renderAuditJsonReport.js +2 -0
- package/dist/src/audits/report/renderAuditTextReport.js +40 -0
- package/dist/src/audits/security/mapSecurityFindingToAuditIssue.js +70 -0
- package/dist/src/audits/security/securityAuditAdapter.js +70 -0
- package/dist/src/audits/security/securityAuditTypes.js +17 -0
- package/dist/src/securityValidation/report/buildSecurityReport.js +31 -0
- package/dist/src/securityValidation/report/writeSecurityReportFiles.js +28 -0
- package/dist/src/securityValidation/validate/resolveTarget.js +9 -0
- package/package.json +6 -8
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.3.3 Batch 1 -- conservative, dependency-free Kotlin source analyzer.
|
|
3
|
+
//
|
|
4
|
+
// There is no Kotlin compiler/parser available in this Node/TypeScript
|
|
5
|
+
// project, and per this batch's scope this analyzer must not add a new
|
|
6
|
+
// parser dependency. Facts are extracted with the same style of
|
|
7
|
+
// brace-depth-tracking line scan as javaAnalyzer.ts -- not a real
|
|
8
|
+
// tokenizer, not an AST, and not a syntax validator.
|
|
9
|
+
//
|
|
10
|
+
// What this DOES extract:
|
|
11
|
+
// - a single `package foo.bar` declaration (no trailing `;` required)
|
|
12
|
+
// - `import foo.bar.Baz` and `import foo.bar.Baz as Alias` (recorded via
|
|
13
|
+
// the existing generic ImportFactKind "static" -- "resolved through a
|
|
14
|
+
// static, syntactic import statement", the same meaning already used
|
|
15
|
+
// by every other analyzer in this project)
|
|
16
|
+
// - class, data class, interface, and object declarations (data class ->
|
|
17
|
+
// DeclarationFactKind "class"; object -> "class", the closest safe
|
|
18
|
+
// existing kind for a singleton declaration -- there is no dedicated
|
|
19
|
+
// object kind in the shared model)
|
|
20
|
+
// - `enum class Foo` -> DeclarationFactKind "enum"
|
|
21
|
+
// - top-level function declarations (`fun foo(...)`) -> kind "function"
|
|
22
|
+
// - member function declarations directly inside a class/interface/
|
|
23
|
+
// object body -> kind "method"
|
|
24
|
+
// - visibility (public/internal/protected/private), folded into the
|
|
25
|
+
// existing `exported` boolean -- see "Visibility mapping" below
|
|
26
|
+
// - line counts, parse/analyze status, and diagnostics for degraded
|
|
27
|
+
// parsing
|
|
28
|
+
//
|
|
29
|
+
// What this explicitly does NOT do: real Kotlin compilation, type
|
|
30
|
+
// inference, extension-function semantic resolution (an extension
|
|
31
|
+
// function's receiver type is not modeled -- only the function's own
|
|
32
|
+
// identifier is recorded as its name), coroutine behavior, multiplatform
|
|
33
|
+
// source-set semantics, Android/Compose lifecycle behavior, runtime
|
|
34
|
+
// behavior, semantic duplicate-implementation proof, or dead-code proof.
|
|
35
|
+
// Property (`val`/`var`) declarations are not extracted (out of this
|
|
36
|
+
// batch's required scope, and harder to distinguish conservatively from
|
|
37
|
+
// local variable declarations via regex alone).
|
|
38
|
+
//
|
|
39
|
+
// Visibility mapping (no SourceFacts schema change):
|
|
40
|
+
// Same reasoning as javaAnalyzer.ts. Kotlin's default (no modifier) is
|
|
41
|
+
// `public`, unlike Java's package-private default -- so `exported` is
|
|
42
|
+
// true unless an explicit `internal`, `protected`, or `private` modifier
|
|
43
|
+
// is present. This intentionally collapses internal/protected/private
|
|
44
|
+
// into a single "not exported" bucket; callers needing that distinction
|
|
45
|
+
// must re-derive it from source.
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export const KOTLIN_ANALYZER_ID = "kotlin-analyzer";
|
|
48
|
+
const SUPPORTED_EXTENSIONS = [".kt", ".kts"];
|
|
49
|
+
// Mirrors typescriptJavaScriptAnalyzer.ts/pythonAnalyzer.ts/javaAnalyzer.ts's
|
|
50
|
+
// bound.
|
|
51
|
+
const MAX_ANALYZABLE_BYTES = 1_000_000;
|
|
52
|
+
const PACKAGE_LINE_PATTERN = /^\s*package\s+([\w.]+)\s*;?\s*$/;
|
|
53
|
+
const IMPORT_LINE_PATTERN = /^\s*import\s+([\w.]+(?:\.\*)?)(?:\s+as\s+(\w+))?\s*;?\s*$/;
|
|
54
|
+
const VISIBILITY_WORDS = "(?:public|internal|protected|private)";
|
|
55
|
+
const OTHER_TYPE_MODIFIER_WORDS = "(?:open|final|abstract|sealed|inner|data|annotation|enum|companion|value|actual|expect)";
|
|
56
|
+
const TYPE_DECL_PATTERN = new RegExp(`^\\s*(${VISIBILITY_WORDS}\\s+)?((?:${OTHER_TYPE_MODIFIER_WORDS}\\s+)*)(class|interface|object)\\s+(\\w+)`);
|
|
57
|
+
// Optional generic receiver/type parameters and an optional extension-
|
|
58
|
+
// function receiver (`ReceiverType.`) before the function name -- the
|
|
59
|
+
// receiver itself is discarded, only the final identifier is kept as the
|
|
60
|
+
// function's name (see header comment).
|
|
61
|
+
const FUN_LINE_PATTERN = new RegExp(`^\\s*(${VISIBILITY_WORDS}\\s+)?(?:(?:override|open|final|abstract|inline|suspend|operator|infix|tailrec|external)\\s+)*fun(?:\\s*<[^>]*>)?\\s+(?:[\\w<>?.]+\\.)?(\\w+)\\s*\\(`);
|
|
62
|
+
export const KOTLIN_ANALYZER = {
|
|
63
|
+
id: KOTLIN_ANALYZER_ID,
|
|
64
|
+
supportedLanguages: ["kotlin"],
|
|
65
|
+
supportedExtensions: SUPPORTED_EXTENSIONS,
|
|
66
|
+
analyzeFile,
|
|
67
|
+
};
|
|
68
|
+
function analyzeFile(input) {
|
|
69
|
+
const { relativePath, language, role, content, inventoryEntry } = input;
|
|
70
|
+
if (Buffer.byteLength(content, "utf8") > MAX_ANALYZABLE_BYTES) {
|
|
71
|
+
return {
|
|
72
|
+
relativePath,
|
|
73
|
+
language,
|
|
74
|
+
role,
|
|
75
|
+
parseStatus: "file-level-only",
|
|
76
|
+
analyzerId: KOTLIN_ANALYZER_ID,
|
|
77
|
+
lineCount: inventoryEntry.lineCount,
|
|
78
|
+
imports: [],
|
|
79
|
+
exports: [],
|
|
80
|
+
declarations: [],
|
|
81
|
+
references: [],
|
|
82
|
+
diagnostics: [
|
|
83
|
+
{
|
|
84
|
+
severity: "info",
|
|
85
|
+
code: "file-too-large-for-analysis",
|
|
86
|
+
message: `File exceeds the ${MAX_ANALYZABLE_BYTES}-byte analyzer bound; only inventory-level facts are available.`,
|
|
87
|
+
path: relativePath,
|
|
88
|
+
analyzerId: KOTLIN_ANALYZER_ID,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const diagnostics = [];
|
|
94
|
+
const { logical, hadUnterminatedBlockComment, hadUnterminatedRawString } = buildLogicalLines(content);
|
|
95
|
+
const imports = [];
|
|
96
|
+
const declarations = [];
|
|
97
|
+
const scopeStack = [];
|
|
98
|
+
let depth = 0;
|
|
99
|
+
for (let i = 0; i < logical.length; i++) {
|
|
100
|
+
const line = logical[i];
|
|
101
|
+
const lineNumber = i + 1;
|
|
102
|
+
if (line.trim().length === 0) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const importMatch = line.match(IMPORT_LINE_PATTERN);
|
|
106
|
+
if (importMatch) {
|
|
107
|
+
imports.push({
|
|
108
|
+
source: importMatch[1],
|
|
109
|
+
kind: "static",
|
|
110
|
+
line: lineNumber,
|
|
111
|
+
...(importMatch[2] ? { importedNames: [importMatch[2]] } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const typeMatch = line.match(TYPE_DECL_PATTERN);
|
|
115
|
+
let isEnumClass = false;
|
|
116
|
+
if (typeMatch) {
|
|
117
|
+
isEnumClass = /(?:^|\s)enum(?:\s|$)/.test(typeMatch[2]);
|
|
118
|
+
}
|
|
119
|
+
if (typeMatch) {
|
|
120
|
+
const rawKeyword = typeMatch[3]; // "class" | "interface" | "object"
|
|
121
|
+
const name = typeMatch[4];
|
|
122
|
+
const exported = typeMatch[1] === undefined || typeMatch[1].trim() === "public";
|
|
123
|
+
declarations.push({
|
|
124
|
+
name,
|
|
125
|
+
kind: isEnumClass ? "enum" : rawKeyword === "interface" ? "interface" : "class",
|
|
126
|
+
exported,
|
|
127
|
+
line: lineNumber,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else if (scopeStack.length > 0 && depth === scopeStack[scopeStack.length - 1].bodyDepth) {
|
|
131
|
+
const funMatch = line.match(FUN_LINE_PATTERN);
|
|
132
|
+
if (funMatch) {
|
|
133
|
+
const exported = funMatch[1] === undefined || funMatch[1].trim() === "public";
|
|
134
|
+
declarations.push({ name: funMatch[2], kind: "method", exported, line: lineNumber });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (scopeStack.length === 0) {
|
|
138
|
+
const funMatch = line.match(FUN_LINE_PATTERN);
|
|
139
|
+
if (funMatch) {
|
|
140
|
+
const exported = funMatch[1] === undefined || funMatch[1].trim() === "public";
|
|
141
|
+
declarations.push({ name: funMatch[2], kind: "function", exported, line: lineNumber });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const depthBefore = depth;
|
|
145
|
+
depth += countChar(line, "{") - countChar(line, "}");
|
|
146
|
+
if (typeMatch) {
|
|
147
|
+
const rawKeyword = typeMatch[3];
|
|
148
|
+
// class/object and interface both get a scope frame ("object" is
|
|
149
|
+
// tracked as a "class"-kind scope for member-function purposes);
|
|
150
|
+
// whether the opening `{` lands on this line or a later one, the
|
|
151
|
+
// eventual `{` bumps depth to exactly depthBefore + 1 either way --
|
|
152
|
+
// same reasoning as javaAnalyzer.ts's scope push.
|
|
153
|
+
const scopeKind = rawKeyword === "interface" ? "interface" : "class";
|
|
154
|
+
scopeStack.push({ kind: scopeKind, bodyDepth: depthBefore + 1 });
|
|
155
|
+
}
|
|
156
|
+
while (scopeStack.length > 0 && depth < scopeStack[scopeStack.length - 1].bodyDepth) {
|
|
157
|
+
scopeStack.pop();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (hadUnterminatedRawString) {
|
|
161
|
+
diagnostics.push({
|
|
162
|
+
severity: "error",
|
|
163
|
+
code: "unterminated-raw-string",
|
|
164
|
+
message: 'File appears to contain an unterminated """ raw string; facts from this best-effort scan may be incomplete.',
|
|
165
|
+
path: relativePath,
|
|
166
|
+
analyzerId: KOTLIN_ANALYZER_ID,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (hadUnterminatedBlockComment) {
|
|
170
|
+
diagnostics.push({
|
|
171
|
+
severity: "error",
|
|
172
|
+
code: "unterminated-block-comment",
|
|
173
|
+
message: "File appears to contain an unterminated /* block comment; facts from this best-effort scan may be incomplete.",
|
|
174
|
+
path: relativePath,
|
|
175
|
+
analyzerId: KOTLIN_ANALYZER_ID,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const parseStatus = hadUnterminatedRawString || hadUnterminatedBlockComment ? "parse-error" : "parsed";
|
|
179
|
+
return {
|
|
180
|
+
relativePath,
|
|
181
|
+
language,
|
|
182
|
+
role,
|
|
183
|
+
parseStatus,
|
|
184
|
+
analyzerId: KOTLIN_ANALYZER_ID,
|
|
185
|
+
lineCount: inventoryEntry.lineCount,
|
|
186
|
+
imports,
|
|
187
|
+
exports: [],
|
|
188
|
+
declarations,
|
|
189
|
+
references: [],
|
|
190
|
+
diagnostics,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function countChar(line, ch) {
|
|
194
|
+
let count = 0;
|
|
195
|
+
for (let i = 0; i < line.length; i++) {
|
|
196
|
+
if (line[i] === ch)
|
|
197
|
+
count += 1;
|
|
198
|
+
}
|
|
199
|
+
return count;
|
|
200
|
+
}
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Comment/string stripping (heuristic, not a tokenizer). Strips `//` line
|
|
203
|
+
// comments and `/* ... */` block comments (which may span multiple lines),
|
|
204
|
+
// blanks out `"..."`/`'...'` single-line literal contents (escape-aware),
|
|
205
|
+
// and blanks out `"""..."""` raw strings (which may span multiple lines,
|
|
206
|
+
// no escape processing inside -- mirrors pythonAnalyzer.ts's triple-quote
|
|
207
|
+
// handling, since Kotlin raw strings share the same "arbitrary content
|
|
208
|
+
// until the next matching triple-quote marker" shape).
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
function buildLogicalLines(content) {
|
|
211
|
+
const rawLines = content.split(/\r\n|\r|\n/);
|
|
212
|
+
const logical = [];
|
|
213
|
+
let inBlockComment = false;
|
|
214
|
+
let inRawString = false;
|
|
215
|
+
for (const rawLine of rawLines) {
|
|
216
|
+
if (inRawString) {
|
|
217
|
+
const closeIdx = rawLine.indexOf('"""');
|
|
218
|
+
if (closeIdx === -1) {
|
|
219
|
+
logical.push("");
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
inRawString = false;
|
|
223
|
+
logical.push(stripLine(rawLine.slice(closeIdx + 3)));
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
logical.push(stripLine(rawLine));
|
|
227
|
+
}
|
|
228
|
+
return { logical, hadUnterminatedBlockComment: inBlockComment, hadUnterminatedRawString: inRawString };
|
|
229
|
+
function stripLine(line) {
|
|
230
|
+
let result = "";
|
|
231
|
+
let i = 0;
|
|
232
|
+
while (i < line.length) {
|
|
233
|
+
if (inBlockComment) {
|
|
234
|
+
const closeIdx = line.indexOf("*/", i);
|
|
235
|
+
if (closeIdx === -1)
|
|
236
|
+
return result;
|
|
237
|
+
inBlockComment = false;
|
|
238
|
+
i = closeIdx + 2;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const ch = line[i];
|
|
242
|
+
if (ch === "/" && line[i + 1] === "/")
|
|
243
|
+
return result;
|
|
244
|
+
if (ch === "/" && line[i + 1] === "*") {
|
|
245
|
+
inBlockComment = true;
|
|
246
|
+
i += 2;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (line.slice(i, i + 3) === '"""') {
|
|
250
|
+
const closeIdx = line.indexOf('"""', i + 3);
|
|
251
|
+
if (closeIdx === -1) {
|
|
252
|
+
inRawString = true;
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
i = closeIdx + 3;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (ch === '"' || ch === "'") {
|
|
259
|
+
const quote = ch;
|
|
260
|
+
let j = i + 1;
|
|
261
|
+
while (j < line.length) {
|
|
262
|
+
if (line[j] === "\\") {
|
|
263
|
+
j += 2;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (line[j] === quote) {
|
|
267
|
+
j += 1;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
j += 1;
|
|
271
|
+
}
|
|
272
|
+
i = j;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
result += ch;
|
|
276
|
+
i += 1;
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { TYPESCRIPT_JAVASCRIPT_ANALYZER } from "./typescriptJavaScriptAnalyzer.js";
|
|
2
|
+
import { PYTHON_ANALYZER } from "./pythonAnalyzer.js";
|
|
3
|
+
import { JAVA_ANALYZER } from "./javaAnalyzer.js";
|
|
4
|
+
import { KOTLIN_ANALYZER } from "./kotlinAnalyzer.js";
|
|
2
5
|
// Validates a list has no duplicate analyzer ids. Later batches call this
|
|
3
6
|
// when assembling their own registries; Batch 2's empty default trivially
|
|
4
7
|
// passes.
|
|
@@ -25,9 +28,15 @@ export function selectLanguageAnalyzer(registry, language, extension) {
|
|
|
25
28
|
}
|
|
26
29
|
return null;
|
|
27
30
|
}
|
|
28
|
-
// v0.3.1 Batch 3 -- registers the TypeScript/JavaScript analyzer.
|
|
29
|
-
//
|
|
30
|
-
// see
|
|
31
|
+
// v0.3.1 Batch 3 -- registers the TypeScript/JavaScript analyzer. v0.3.2
|
|
32
|
+
// Batch 1 adds the Python analyzer alongside it. v0.3.3 Batch 1 adds Java
|
|
33
|
+
// and Kotlin (see javaAnalyzer.ts/kotlinAnalyzer.ts) -- no other language
|
|
34
|
+
// remains fallback-only by design change here; any language not in this
|
|
35
|
+
// list still degrades through collectSourceFacts.ts's existing fallback
|
|
36
|
+
// policy unchanged.
|
|
31
37
|
export const DEFAULT_LANGUAGE_ANALYZER_REGISTRY = createLanguageAnalyzerRegistry([
|
|
32
38
|
TYPESCRIPT_JAVASCRIPT_ANALYZER,
|
|
39
|
+
PYTHON_ANALYZER,
|
|
40
|
+
JAVA_ANALYZER,
|
|
41
|
+
KOTLIN_ANALYZER,
|
|
33
42
|
]);
|
|
@@ -356,7 +356,7 @@ function classifyFile(relativePath, basename, extension) {
|
|
|
356
356
|
if (relativePath.startsWith(".github/workflows/") && (extension === ".yml" || extension === ".yaml")) {
|
|
357
357
|
return "ci";
|
|
358
358
|
}
|
|
359
|
-
if (isTestFile(relativePath, basename))
|
|
359
|
+
if (isTestFile(relativePath, basename, extension))
|
|
360
360
|
return "tests";
|
|
361
361
|
// Top-level scripts/ directory -- classified before generic "source" so
|
|
362
362
|
// scripts (which are also .ts/.js) land in their own category.
|
|
@@ -374,11 +374,76 @@ function classifyFile(relativePath, basename, extension) {
|
|
|
374
374
|
return "source";
|
|
375
375
|
return "unknown";
|
|
376
376
|
}
|
|
377
|
-
|
|
377
|
+
// v0.3.2 Batch 1 -- conservative pytest naming conventions. Gated to .py so
|
|
378
|
+
// this can never reclassify a non-Python file. "tests/**/*.py" and
|
|
379
|
+
// "**/tests/**/*.py" are already covered by the generic, extension-agnostic
|
|
380
|
+
// check above; only the singular "test/" directory and the test_*.py /
|
|
381
|
+
// *_test.py filename conventions are Python-specific additions here.
|
|
382
|
+
function isPythonTestFile(relativePath, basename) {
|
|
383
|
+
if (relativePath.startsWith("test/") || relativePath.includes("/test/"))
|
|
384
|
+
return true;
|
|
385
|
+
return /^test_.*\.py$/.test(basename) || /^.*_test\.py$/.test(basename);
|
|
386
|
+
}
|
|
387
|
+
// v0.3.3 Batch 1 -- conservative JVM (Java/Kotlin) test naming/source-set
|
|
388
|
+
// conventions. Gated to .java/.kt so this can never reclassify an unrelated
|
|
389
|
+
// file. "tests/**/*.java" and "tests/**/*.kt" are already covered by the
|
|
390
|
+
// generic, extension-agnostic check above; this adds the Gradle/Maven
|
|
391
|
+
// standard "src/test/{java,kotlin}" source-set layout, the singular "test/"
|
|
392
|
+
// directory convention (same style as isPythonTestFile's "test/" check
|
|
393
|
+
// above), and the "*Test(s).java" / "*Test(s).kt" / "*Spec.kt" filename
|
|
394
|
+
// conventions.
|
|
395
|
+
function isJvmTestFile(relativePath, basename, extension) {
|
|
396
|
+
if (relativePath.includes("src/test/java/") || relativePath.includes("src/test/kotlin/"))
|
|
397
|
+
return true;
|
|
398
|
+
if (relativePath.startsWith("test/") || relativePath.includes("/test/"))
|
|
399
|
+
return true;
|
|
400
|
+
if (extension === ".java")
|
|
401
|
+
return /Tests?\.java$/.test(basename);
|
|
402
|
+
if (extension === ".kt")
|
|
403
|
+
return /Tests?\.kt$/.test(basename) || /Spec\.kt$/.test(basename);
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
function isTestFile(relativePath, basename, extension) {
|
|
378
407
|
if (relativePath.startsWith("tests/") || relativePath.includes("/tests/"))
|
|
379
408
|
return true;
|
|
380
|
-
|
|
409
|
+
if (/\.(test|spec)\.[jt]sx?$/.test(basename))
|
|
410
|
+
return true;
|
|
411
|
+
if (extension === ".py" && isPythonTestFile(relativePath, basename))
|
|
412
|
+
return true;
|
|
413
|
+
if ((extension === ".java" || extension === ".kt") && isJvmTestFile(relativePath, basename, extension))
|
|
414
|
+
return true;
|
|
415
|
+
return false;
|
|
381
416
|
}
|
|
417
|
+
// v0.3.2 Batch 1 -- recognized Python project/config metadata filenames.
|
|
418
|
+
// Matched by basename only (same convention as PACKAGE_FILE_NAMES above),
|
|
419
|
+
// not restricted to root, since these are specific enough not to collide
|
|
420
|
+
// with unrelated fixture/benchmark content. Deliberately narrow: does not
|
|
421
|
+
// match arbitrary *.toml/*.ini/*.txt files, only these exact names.
|
|
422
|
+
const PYTHON_PROJECT_METADATA_FILE_NAMES = new Set([
|
|
423
|
+
"pyproject.toml",
|
|
424
|
+
"requirements.txt",
|
|
425
|
+
"setup.cfg",
|
|
426
|
+
"tox.ini",
|
|
427
|
+
"pytest.ini",
|
|
428
|
+
]);
|
|
429
|
+
// v0.3.3 Batch 1 -- recognized JVM (Gradle/Maven) project/build metadata
|
|
430
|
+
// filenames. Same narrow, basename-only convention as
|
|
431
|
+
// PYTHON_PROJECT_METADATA_FILE_NAMES above: does not match arbitrary *.xml/
|
|
432
|
+
// *.properties files, only these exact names. `build.gradle.kts` and
|
|
433
|
+
// `settings.gradle.kts` matter in particular: without this, a Gradle Kotlin
|
|
434
|
+
// DSL build script would otherwise fall through to the generic
|
|
435
|
+
// KNOWN_SOURCE_EXTENSIONS ".kts" -> "source" classification below and be fed
|
|
436
|
+
// to the Kotlin source-facts analyzer as if it were application code.
|
|
437
|
+
const JVM_PROJECT_METADATA_FILE_NAMES = new Set([
|
|
438
|
+
"build.gradle",
|
|
439
|
+
"build.gradle.kts",
|
|
440
|
+
"settings.gradle",
|
|
441
|
+
"settings.gradle.kts",
|
|
442
|
+
"pom.xml",
|
|
443
|
+
"gradle.properties",
|
|
444
|
+
"gradlew",
|
|
445
|
+
"gradlew.bat",
|
|
446
|
+
]);
|
|
382
447
|
function isConfigFile(relativePath, basename, extension) {
|
|
383
448
|
if (/^tsconfig(\..+)?\.json$/.test(basename))
|
|
384
449
|
return true;
|
|
@@ -388,6 +453,14 @@ function isConfigFile(relativePath, basename, extension) {
|
|
|
388
453
|
return true;
|
|
389
454
|
if (basename === ".editorconfig" || basename === ".gitignore" || basename === ".npmignore")
|
|
390
455
|
return true;
|
|
456
|
+
// v0.3.2 Batch 1 -- setup.py has a .py extension and would otherwise fall
|
|
457
|
+
// through to the generic KNOWN_SOURCE_EXTENSIONS "source" classification
|
|
458
|
+
// below; the other names have no source-extension conflict but are listed
|
|
459
|
+
// here for the same "project metadata, not source" reasoning.
|
|
460
|
+
if (basename === "setup.py" || PYTHON_PROJECT_METADATA_FILE_NAMES.has(basename))
|
|
461
|
+
return true;
|
|
462
|
+
if (JVM_PROJECT_METADATA_FILE_NAMES.has(basename))
|
|
463
|
+
return true;
|
|
391
464
|
// Root-level dotfile configs (e.g. .prettierrc, .babelrc) not already
|
|
392
465
|
// covered above -- deliberately conservative (root-only, not recursive)
|
|
393
466
|
// to avoid misclassifying dotfiles inside fixture/benchmark projects.
|