@chem-x/starter-kit 26.9.28-1234 → 26.10.4-235
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/cli/languages.js +8 -0
- package/cli/languages.spec.js +21 -0
- package/cli/polyglot.spec.js +111 -0
- package/cli/search-ast.js +54 -0
- package/cli/search.js +2 -1
- package/cli/search.spec.js +15 -0
- package/package.json +1 -1
package/cli/languages.js
CHANGED
|
@@ -76,6 +76,14 @@ export const LANGUAGE_DEFINITIONS = {
|
|
|
76
76
|
parser: 'jvm',
|
|
77
77
|
commentPrefix: '//',
|
|
78
78
|
blockComment: { start: '/*', end: '*/' }
|
|
79
|
+
},
|
|
80
|
+
cpp: {
|
|
81
|
+
id: 'cpp',
|
|
82
|
+
name: 'C / C++',
|
|
83
|
+
extensions: new Set(['.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hxx', '.hh', '.inl']),
|
|
84
|
+
parser: 'cpp',
|
|
85
|
+
commentPrefix: '//',
|
|
86
|
+
blockComment: { start: '/*', end: '*/' }
|
|
79
87
|
}
|
|
80
88
|
};
|
|
81
89
|
|
package/cli/languages.spec.js
CHANGED
|
@@ -42,3 +42,24 @@ test('languages: resolves language metadata', () => {
|
|
|
42
42
|
assert.equal(py.id, 'python');
|
|
43
43
|
assert.equal(py.commentPrefix, '#');
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
test('languages: recognizes C and C++ source extensions', () => {
|
|
47
|
+
assert.equal(isSourceFile('StreamDecoder.cpp'), true);
|
|
48
|
+
assert.equal(isSourceFile('StreamDecoder.cc'), true);
|
|
49
|
+
assert.equal(isSourceFile('StreamDecoder.cxx'), true);
|
|
50
|
+
assert.equal(isSourceFile('StreamDecoder.hpp'), true);
|
|
51
|
+
assert.equal(isSourceFile('StreamDecoder.h'), true);
|
|
52
|
+
assert.equal(isSourceFile('legacy_shim.c'), true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('languages: maps C and C++ extensions to the cpp language', () => {
|
|
56
|
+
assert.equal(getLanguageForFile('StreamDecoder.cpp').id, 'cpp');
|
|
57
|
+
assert.equal(getLanguageForFile('StreamDecoder.h').id, 'cpp');
|
|
58
|
+
assert.equal(getLanguageForFile('legacy_shim.c').id, 'cpp');
|
|
59
|
+
assert.equal(LANGUAGE_DEFINITIONS.cpp.name, 'C / C++');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('languages: C and C++ are not babel parsable', () => {
|
|
63
|
+
assert.equal(isBabelParsable('StreamDecoder.cpp'), false);
|
|
64
|
+
assert.equal(isBabelParsable('StreamDecoder.h'), false);
|
|
65
|
+
});
|
package/cli/polyglot.spec.js
CHANGED
|
@@ -109,3 +109,114 @@ func TestDummy(t *testing.T) {
|
|
|
109
109
|
assert.equal(goViolations.some((v) => v.rule === 'SYNTAX_PARSE_ERROR'), false);
|
|
110
110
|
assert.equal(goViolations.some((v) => v.rule === 'SYNTHETIC_MOCK_DATA'), true);
|
|
111
111
|
});
|
|
112
|
+
|
|
113
|
+
test('polyglot: extracts C++ classes, structs, functions and includes', () => {
|
|
114
|
+
const cppCode = `
|
|
115
|
+
#include <memory>
|
|
116
|
+
#include "vendor/codec.h"
|
|
117
|
+
|
|
118
|
+
namespace audio {
|
|
119
|
+
|
|
120
|
+
struct FrameHeader {
|
|
121
|
+
uint32_t size;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
enum class Codec { Opus, Flac };
|
|
125
|
+
|
|
126
|
+
class StreamDecoder : public IDecoder {
|
|
127
|
+
public:
|
|
128
|
+
bool DecodeFrame(const FrameHeader& header);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
bool StreamDecoder::DecodeFrame(const FrameHeader& header) {
|
|
132
|
+
return header.size > 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
} // namespace audio
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const meta = extractAstMetadata(cppCode, 'src/audio/StreamDecoder.cpp');
|
|
139
|
+
const names = meta.symbols.map((s) => s.name);
|
|
140
|
+
assert.ok(names.includes('StreamDecoder'), 'must extract class StreamDecoder');
|
|
141
|
+
assert.ok(names.includes('FrameHeader'), 'must extract struct FrameHeader');
|
|
142
|
+
assert.ok(names.includes('Codec'), 'must extract enum class Codec');
|
|
143
|
+
assert.ok(names.includes('DecodeFrame'), 'must extract method DecodeFrame');
|
|
144
|
+
assert.ok(meta.imports.some((i) => i.sourceModule === 'vendor/codec.h'), 'must map #include to imports');
|
|
145
|
+
assert.ok(meta.imports.some((i) => i.sourceModule === 'memory'), 'must map angle-bracket includes');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('polyglot: audits C++ without Babel syntax errors', () => {
|
|
149
|
+
const cppCode = `
|
|
150
|
+
#include <string>
|
|
151
|
+
void Connect() {
|
|
152
|
+
std::string email = "user@example.com";
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
const violations = auditCode(cppCode, 'src/net/Client.cpp', 'src/net/Client.cpp');
|
|
156
|
+
assert.equal(violations.some((v) => v.rule === 'SYNTAX_PARSE_ERROR'), false, 'C++ must not trigger Babel SYNTAX_PARSE_ERROR');
|
|
157
|
+
assert.equal(violations.some((v) => v.rule === 'SYNTHETIC_MOCK_DATA'), true, 'must detect placeholder email in C++');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('polyglot: extracts Rust items and use imports', () => {
|
|
161
|
+
const rustCode = `
|
|
162
|
+
use std::sync::Arc;
|
|
163
|
+
|
|
164
|
+
pub struct Decoder {
|
|
165
|
+
buffer: Vec<u8>,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pub enum State { Idle, Running }
|
|
169
|
+
|
|
170
|
+
pub trait Sink {
|
|
171
|
+
fn write(&self, data: &[u8]);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
impl Decoder {
|
|
175
|
+
pub fn new() -> Self { Decoder { buffer: Vec::new() } }
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
const meta = extractAstMetadata(rustCode, 'src/decoder.rs');
|
|
179
|
+
const names = meta.symbols.map((s) => s.name);
|
|
180
|
+
assert.ok(names.includes('Decoder'), 'must extract pub struct');
|
|
181
|
+
assert.ok(names.includes('State'), 'must extract pub enum');
|
|
182
|
+
assert.ok(names.includes('Sink'), 'must extract pub trait');
|
|
183
|
+
assert.ok(names.includes('new'), 'must extract fn');
|
|
184
|
+
assert.ok(meta.imports.some((i) => i.sourceModule === 'std::sync::Arc'), 'must map use statements');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('polyglot: extracts Kotlin declarations and imports', () => {
|
|
188
|
+
const kotlinCode = `
|
|
189
|
+
import kotlinx.coroutines.flow.Flow
|
|
190
|
+
|
|
191
|
+
class ConsentRepository(private val api: Api) {
|
|
192
|
+
fun observe(): Flow<List<Consent>> = api.stream()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
data class Consent(val id: String)
|
|
196
|
+
|
|
197
|
+
object Registry {
|
|
198
|
+
fun lookup(id: String) = id
|
|
199
|
+
}
|
|
200
|
+
`;
|
|
201
|
+
const meta = extractAstMetadata(kotlinCode, 'src/main/kotlin/ConsentRepository.kt');
|
|
202
|
+
const names = meta.symbols.map((s) => s.name);
|
|
203
|
+
assert.ok(names.includes('ConsentRepository'), 'must extract class');
|
|
204
|
+
assert.ok(names.includes('Consent'), 'must extract data class');
|
|
205
|
+
assert.ok(names.includes('Registry'), 'must extract object');
|
|
206
|
+
assert.ok(names.includes('observe'), 'must extract fun');
|
|
207
|
+
assert.ok(meta.imports.some((i) => i.sourceModule === 'kotlinx.coroutines.flow.Flow'), 'must map kotlin imports');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('polyglot: declaration keywords never leak in as symbol names', () => {
|
|
211
|
+
const cppCode = `
|
|
212
|
+
enum class Codec { Opus, Flac };
|
|
213
|
+
struct Frame { int size; };
|
|
214
|
+
namespace audio { }
|
|
215
|
+
`;
|
|
216
|
+
const meta = extractAstMetadata(cppCode, 'src/audio/Codec.h');
|
|
217
|
+
const names = meta.symbols.map((s) => s.name);
|
|
218
|
+
assert.equal(names.includes('class'), false, 'bare "class" must not be extracted as a symbol');
|
|
219
|
+
assert.equal(names.includes('struct'), false, 'bare "struct" must not be extracted as a symbol');
|
|
220
|
+
assert.ok(names.includes('Codec'));
|
|
221
|
+
assert.ok(names.includes('Frame'));
|
|
222
|
+
});
|
package/cli/search-ast.js
CHANGED
|
@@ -21,6 +21,17 @@ export const resolveArchitectureTier = (relativePath) => {
|
|
|
21
21
|
return 'utility';
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
const CPP_NON_DECLARATION_KEYWORDS = new Set([
|
|
25
|
+
'if', 'for', 'while', 'switch', 'catch', 'return', 'sizeof', 'else', 'do', 'throw', 'new', 'delete'
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Declaration keywords are never symbol names. Guards cross-language regex overlap,
|
|
29
|
+
// e.g. the Rust `enum` pattern matching C++ `enum class Codec` and capturing `class`.
|
|
30
|
+
const RESERVED_SYMBOL_NAMES = new Set([
|
|
31
|
+
'class', 'struct', 'enum', 'union', 'interface', 'object', 'fun', 'trait', 'impl', 'mod',
|
|
32
|
+
'namespace', 'typename', 'template', 'public', 'private', 'protected', 'static', 'const'
|
|
33
|
+
]);
|
|
34
|
+
|
|
24
35
|
const extractRegexFallback = (content, filePath = '') => {
|
|
25
36
|
const symbols = [];
|
|
26
37
|
const imports = [];
|
|
@@ -47,6 +58,49 @@ const extractRegexFallback = (content, filePath = '') => {
|
|
|
47
58
|
symbols.push({ name: m[1], kind: 'function', isExport: true, startLine: 1, endLine: 1, signature: '' });
|
|
48
59
|
}
|
|
49
60
|
|
|
61
|
+
const cppTypeMatches = content.matchAll(/\b(?:class|struct|union|enum(?:\s+class)?)\s+([A-Za-z_][A-Za-z0-9_]*)/g);
|
|
62
|
+
for (const m of cppTypeMatches) {
|
|
63
|
+
if (!RESERVED_SYMBOL_NAMES.has(m[1])) {
|
|
64
|
+
symbols.push({ name: m[1], kind: 'class', isExport: true, startLine: 1, endLine: 1, signature: '' });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cppQualifiedMatches = content.matchAll(/\b[A-Za-z_][A-Za-z0-9_]*\s*::\s*([A-Za-z_~][A-Za-z0-9_]*)\s*\(/g);
|
|
69
|
+
for (const m of cppQualifiedMatches) {
|
|
70
|
+
symbols.push({ name: m[1], kind: 'function', isExport: true, startLine: 1, endLine: 1, signature: '' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const cppDeclMatches = content.matchAll(/^[ \t]*(?:[A-Za-z_][A-Za-z0-9_:<>,*& \t]*?)\s+\*?([A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*(?:const\s*)?[;{]/gm);
|
|
74
|
+
for (const m of cppDeclMatches) {
|
|
75
|
+
if (!CPP_NON_DECLARATION_KEYWORDS.has(m[1])) {
|
|
76
|
+
symbols.push({ name: m[1], kind: 'function', isExport: true, startLine: 1, endLine: 1, signature: '' });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rustMatches = content.matchAll(/\b(?:pub\s+)?(?:struct|enum|trait|impl|mod|fn)\s+([A-Za-z_][A-Za-z0-9_]*)/g);
|
|
81
|
+
for (const m of rustMatches) {
|
|
82
|
+
if (!RESERVED_SYMBOL_NAMES.has(m[1])) {
|
|
83
|
+
symbols.push({ name: m[1], kind: 'symbol', isExport: true, startLine: 1, endLine: 1, signature: '' });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const kotlinMatches = content.matchAll(/\b(?:data\s+|sealed\s+|open\s+|abstract\s+|inner\s+)?(?:class|object|interface|fun)\s+([A-Za-z_][A-Za-z0-9_]*)/g);
|
|
88
|
+
for (const m of kotlinMatches) {
|
|
89
|
+
if (!RESERVED_SYMBOL_NAMES.has(m[1])) {
|
|
90
|
+
symbols.push({ name: m[1], kind: 'symbol', isExport: true, startLine: 1, endLine: 1, signature: '' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const cppIncludes = content.matchAll(/#include\s*[<"]([^>"]+)[>"]/g);
|
|
95
|
+
for (const m of cppIncludes) {
|
|
96
|
+
imports.push({ importedSymbol: '*', sourceModule: m[1], line: 1 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const rustUses = content.matchAll(/\buse\s+([A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)+)/g);
|
|
100
|
+
for (const m of rustUses) {
|
|
101
|
+
imports.push({ importedSymbol: '*', sourceModule: m[1], line: 1 });
|
|
102
|
+
}
|
|
103
|
+
|
|
50
104
|
const csImports = content.matchAll(/using\s+([A-Za-z0-9_.]+);/g);
|
|
51
105
|
for (const m of csImports) {
|
|
52
106
|
imports.push({ importedSymbol: '*', sourceModule: m[1], line: 1 });
|
package/cli/search.js
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { runGenerateWizard } from './generator.js';
|
|
37
37
|
import { runMutatorCli } from './mutators.js';
|
|
38
38
|
import { toColumnar } from './columnar.js';
|
|
39
|
+
import { resolveTargetDir } from './path-scope.js';
|
|
39
40
|
import { ANSI } from './theme.js';
|
|
40
41
|
|
|
41
42
|
export { toColumnar, fromColumnar } from './columnar.js';
|
|
@@ -207,7 +208,7 @@ const formatTierBadge = (tier) => {
|
|
|
207
208
|
return map[tier] || `${ANSI.DIM}[${tier}]${ANSI.RESET}`;
|
|
208
209
|
};
|
|
209
210
|
|
|
210
|
-
export { resolveTargetDir }
|
|
211
|
+
export { resolveTargetDir };
|
|
211
212
|
|
|
212
213
|
export const printSearchHelp = () => {
|
|
213
214
|
const BOLD = '\x1b[1m';
|
package/cli/search.spec.js
CHANGED
|
@@ -277,3 +277,18 @@ test('hybrid search: natural language "toggle a task item" ranks useTaskListCont
|
|
|
277
277
|
} catch {}
|
|
278
278
|
});
|
|
279
279
|
|
|
280
|
+
|
|
281
|
+
test('runSearch: resolveTargetDir is bound in module scope, not only re-exported', async () => {
|
|
282
|
+
const { runSearch } = await import('./search.js');
|
|
283
|
+
let caught = null;
|
|
284
|
+
try {
|
|
285
|
+
await runSearch(['__chemx_nonexistent_symbol__'], false);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
caught = err;
|
|
288
|
+
}
|
|
289
|
+
assert.equal(
|
|
290
|
+
caught instanceof ReferenceError,
|
|
291
|
+
false,
|
|
292
|
+
`runSearch threw a ReferenceError: ${caught && caught.message}`
|
|
293
|
+
);
|
|
294
|
+
});
|