c-next 0.2.11 → 0.2.13
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 +80 -649
- package/dist/index.js +8273 -6357
- package/dist/index.js.map +4 -4
- package/grammar/C.g4 +17 -4
- package/package.json +3 -3
- package/src/__tests__/index.test.ts +1 -1
- package/src/cli/CleanCommand.ts +8 -12
- package/src/cli/Cli.ts +29 -6
- package/src/cli/Runner.ts +42 -62
- package/src/cli/__tests__/CleanCommand.test.ts +10 -10
- package/src/cli/__tests__/Cli.test.ts +59 -7
- package/src/cli/__tests__/ConfigPrinter.test.ts +12 -12
- package/src/cli/__tests__/PathNormalizer.test.ts +5 -5
- package/src/cli/__tests__/Runner.test.ts +108 -82
- package/src/cli/serve/ServeCommand.ts +1 -1
- package/src/cli/types/ICliConfig.ts +2 -2
- package/src/lib/parseWithSymbols.ts +21 -21
- package/src/transpiler/Transpiler.ts +99 -46
- package/src/transpiler/__tests__/DualCodePaths.test.ts +29 -29
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +244 -72
- package/src/transpiler/__tests__/Transpiler.test.ts +32 -72
- package/src/transpiler/__tests__/determineProjectRoot.test.ts +30 -28
- package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +1 -1
- package/src/transpiler/data/CNextMarkerDetector.ts +34 -0
- package/src/transpiler/data/CppEntryPointScanner.ts +174 -0
- package/src/transpiler/data/FileDiscovery.ts +2 -105
- package/src/transpiler/data/InputExpansion.ts +37 -81
- package/src/transpiler/data/__tests__/CNextMarkerDetector.test.ts +62 -0
- package/src/transpiler/data/__tests__/CppEntryPointScanner.test.ts +239 -0
- package/src/transpiler/data/__tests__/FileDiscovery.test.ts +45 -191
- package/src/transpiler/data/__tests__/InputExpansion.test.ts +36 -204
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +2 -2
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +4 -5
- package/src/transpiler/logic/parser/c/grammar/C.interp +33 -3
- package/src/transpiler/logic/parser/c/grammar/C.tokens +237 -207
- package/src/transpiler/logic/parser/c/grammar/CLexer.interp +48 -3
- package/src/transpiler/logic/parser/c/grammar/CLexer.tokens +237 -207
- package/src/transpiler/logic/parser/c/grammar/CLexer.ts +702 -611
- package/src/transpiler/logic/parser/c/grammar/CParser.ts +1221 -1107
- package/src/transpiler/logic/symbols/SymbolTable.ts +147 -73
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +157 -14
- package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +3 -3
- package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +7 -37
- package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +6 -6
- package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +28 -27
- package/src/transpiler/logic/symbols/cnext/index.ts +4 -4
- package/src/transpiler/logic/symbols/cnext/utils/SymbolNameUtils.ts +5 -5
- package/src/transpiler/output/codegen/CodeGenerator.ts +16 -1
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +15 -0
- package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +3 -3
- package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +14 -14
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +15 -3
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +14 -6
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +1 -0
- package/src/transpiler/output/codegen/types/IFunctionContextCallbacks.ts +2 -0
- package/src/transpiler/output/codegen/utils/QualifiedNameGenerator.ts +7 -7
- package/src/transpiler/output/codegen/utils/__tests__/QualifiedNameGenerator.test.ts +3 -3
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +10 -1
- package/src/transpiler/output/headers/HeaderGenerator.ts +3 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +6 -2
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +16 -0
- package/src/transpiler/output/headers/adapters/HeaderSymbolAdapter.ts +19 -19
- package/src/transpiler/output/headers/adapters/__tests__/HeaderSymbolAdapter.test.ts +5 -5
- package/src/transpiler/state/SymbolRegistry.ts +10 -12
- package/src/transpiler/state/__tests__/SymbolRegistry.test.ts +11 -13
- package/src/transpiler/types/ICachedFileEntry.ts +4 -0
- package/src/transpiler/types/IPipelineFile.ts +3 -0
- package/src/transpiler/types/ITranspilerConfig.ts +2 -2
- package/src/transpiler/types/symbols/IScopeSymbol.ts +1 -1
- package/src/utils/FunctionUtils.ts +3 -3
- package/src/utils/__tests__/FunctionUtils.test.ts +6 -4
- package/src/utils/cache/CacheManager.ts +28 -15
- package/src/utils/cache/__tests__/CacheManager.test.ts +6 -4
- package/src/transpiler/data/types/IDiscoveryOptions.ts +0 -15
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import IncludeDiscovery from "./IncludeDiscovery";
|
|
3
|
+
import CNextMarkerDetector from "./CNextMarkerDetector";
|
|
4
|
+
import IFileSystem from "../types/IFileSystem";
|
|
5
|
+
import NodeFileSystem from "../NodeFileSystem";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Result of scanning a C/C++ entry point for C-Next sources.
|
|
9
|
+
*/
|
|
10
|
+
interface IScanResult {
|
|
11
|
+
/** Absolute paths to discovered .cnx source files */
|
|
12
|
+
cnextSources: string[];
|
|
13
|
+
/** Errors encountered (e.g., missing .cnx files referenced by markers) */
|
|
14
|
+
errors: string[];
|
|
15
|
+
/** Warnings encountered (e.g., includes that couldn't be resolved) */
|
|
16
|
+
warnings: string[];
|
|
17
|
+
/** True if no C-Next markers were found (not an error, just informational) */
|
|
18
|
+
noCNextFound: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Scans C/C++ entry point files to discover C-Next source dependencies.
|
|
23
|
+
*
|
|
24
|
+
* Walks the include tree starting from a C/C++ file, looks for C-Next
|
|
25
|
+
* generation markers in headers, and extracts the source .cnx paths.
|
|
26
|
+
* Follows includes transitively through discovered .cnx files.
|
|
27
|
+
*
|
|
28
|
+
* Used by the C/C++ entry point feature (ADR-XXX) to enable compilation
|
|
29
|
+
* where the entry point is a C/C++ file that #includes C-Next generated headers.
|
|
30
|
+
*/
|
|
31
|
+
class CppEntryPointScanner {
|
|
32
|
+
private readonly fs: IFileSystem;
|
|
33
|
+
private readonly searchPaths: string[];
|
|
34
|
+
private readonly visited = new Set<string>();
|
|
35
|
+
private readonly cnextSources = new Set<string>();
|
|
36
|
+
private readonly errors: string[] = [];
|
|
37
|
+
private readonly warnings: string[] = [];
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
searchPaths: string[],
|
|
41
|
+
fs: IFileSystem = NodeFileSystem.instance,
|
|
42
|
+
) {
|
|
43
|
+
this.searchPaths = searchPaths;
|
|
44
|
+
this.fs = fs;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Scan an entry point file for C-Next source dependencies.
|
|
49
|
+
*
|
|
50
|
+
* @param entryPath - Path to the C/C++ entry point file
|
|
51
|
+
* @returns Scan result with discovered sources, errors, and warnings
|
|
52
|
+
*/
|
|
53
|
+
scan(entryPath: string): IScanResult {
|
|
54
|
+
const absolutePath = resolve(entryPath);
|
|
55
|
+
this._scanFile(absolutePath);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
cnextSources: Array.from(this.cnextSources),
|
|
59
|
+
errors: this.errors,
|
|
60
|
+
warnings: this.warnings,
|
|
61
|
+
noCNextFound: this.cnextSources.size === 0 && this.errors.length === 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Scan a file for includes and process them.
|
|
67
|
+
*/
|
|
68
|
+
private _scanFile(filePath: string): void {
|
|
69
|
+
if (this.visited.has(filePath)) return;
|
|
70
|
+
this.visited.add(filePath);
|
|
71
|
+
|
|
72
|
+
let content: string;
|
|
73
|
+
try {
|
|
74
|
+
content = this.fs.readFile(filePath);
|
|
75
|
+
} catch {
|
|
76
|
+
this.warnings.push(`Warning: Could not read ${filePath}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const includes = IncludeDiscovery.extractIncludesWithInfo(content);
|
|
81
|
+
const fileDir = dirname(filePath);
|
|
82
|
+
const localSearchPaths = [fileDir, ...this.searchPaths];
|
|
83
|
+
|
|
84
|
+
for (const includeInfo of includes) {
|
|
85
|
+
this._processInclude(includeInfo, localSearchPaths, filePath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Process a single include directive.
|
|
91
|
+
*/
|
|
92
|
+
private _processInclude(
|
|
93
|
+
includeInfo: { path: string; isLocal: boolean },
|
|
94
|
+
searchPaths: string[],
|
|
95
|
+
fromFile: string,
|
|
96
|
+
): void {
|
|
97
|
+
const resolved = IncludeDiscovery.resolveInclude(
|
|
98
|
+
includeInfo.path,
|
|
99
|
+
searchPaths,
|
|
100
|
+
this.fs,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (!resolved) {
|
|
104
|
+
// Only warn for local includes that couldn't be resolved
|
|
105
|
+
// System includes (like <stdio.h>) are expected to not be found
|
|
106
|
+
if (includeInfo.isLocal) {
|
|
107
|
+
this.warnings.push(
|
|
108
|
+
`Warning: #include "${includeInfo.path}" not found (from ${fromFile})`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const absolutePath = resolve(resolved);
|
|
115
|
+
if (this.visited.has(absolutePath)) return;
|
|
116
|
+
|
|
117
|
+
let headerContent: string;
|
|
118
|
+
try {
|
|
119
|
+
headerContent = this.fs.readFile(absolutePath);
|
|
120
|
+
} catch {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check if this is a C-Next generated header
|
|
125
|
+
const sourcePath = CNextMarkerDetector.extractSourcePath(headerContent);
|
|
126
|
+
if (sourcePath) {
|
|
127
|
+
// New-style marker with source path
|
|
128
|
+
this._handleCNextMarker(sourcePath, absolutePath);
|
|
129
|
+
} else if (CNextMarkerDetector.isCNextGenerated(headerContent)) {
|
|
130
|
+
// Old-style marker without source path - treat as leaf node
|
|
131
|
+
// (we know it's C-Next generated but can't determine the source)
|
|
132
|
+
this.visited.add(absolutePath);
|
|
133
|
+
} else {
|
|
134
|
+
// Not a C-Next generated header - scan it for nested includes
|
|
135
|
+
this._scanFile(absolutePath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Handle a C-Next generation marker found in a header.
|
|
141
|
+
*
|
|
142
|
+
* Resolves the source path by first checking relative to the header location,
|
|
143
|
+
* then searching the include paths. This supports both default behavior (header
|
|
144
|
+
* next to source) and --header-out (header in separate directory).
|
|
145
|
+
*/
|
|
146
|
+
private _handleCNextMarker(sourcePath: string, headerPath: string): void {
|
|
147
|
+
const headerDir = dirname(headerPath);
|
|
148
|
+
let absoluteSourcePath = resolve(join(headerDir, sourcePath));
|
|
149
|
+
|
|
150
|
+
// If not found next to header, search include paths (supports --header-out)
|
|
151
|
+
if (!this.fs.exists(absoluteSourcePath)) {
|
|
152
|
+
for (const searchPath of this.searchPaths) {
|
|
153
|
+
const candidate = resolve(join(searchPath, sourcePath));
|
|
154
|
+
if (this.fs.exists(candidate)) {
|
|
155
|
+
absoluteSourcePath = candidate;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!this.fs.exists(absoluteSourcePath)) {
|
|
162
|
+
this.errors.push(
|
|
163
|
+
`C-Next source not found: ${sourcePath} (referenced by ${headerPath})`,
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.cnextSources.add(absoluteSourcePath);
|
|
169
|
+
// Scan the .cnx file for its own includes (transitive discovery)
|
|
170
|
+
this._scanFile(absoluteSourcePath);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default CppEntryPointScanner;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File Discovery
|
|
3
|
-
*
|
|
3
|
+
* Classifies source files by type
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import fg from "fast-glob";
|
|
7
6
|
import { extname, resolve } from "node:path";
|
|
8
7
|
import EFileType from "./types/EFileType";
|
|
9
8
|
import IDiscoveredFile from "./types/IDiscoveredFile";
|
|
10
|
-
import IDiscoveryOptions from "./types/IDiscoveryOptions";
|
|
11
9
|
import IFileSystem from "../types/IFileSystem";
|
|
12
10
|
import NodeFileSystem from "../NodeFileSystem";
|
|
13
11
|
|
|
@@ -31,34 +29,9 @@ const EXTENSION_MAP: Record<string, EFileType> = {
|
|
|
31
29
|
};
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
|
-
*
|
|
35
|
-
* Issue #355: Exclude .pio/build (compiled artifacts) but allow .pio/libdeps (library headers)
|
|
36
|
-
*/
|
|
37
|
-
const DEFAULT_IGNORE_GLOBS = [
|
|
38
|
-
"**/node_modules/**",
|
|
39
|
-
"**/.git/**",
|
|
40
|
-
"**/.build/**",
|
|
41
|
-
"**/.pio/build/**",
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Discovers source files in directories
|
|
32
|
+
* Classifies and discovers source files
|
|
46
33
|
*/
|
|
47
34
|
class FileDiscovery {
|
|
48
|
-
/**
|
|
49
|
-
* Convert RegExp patterns to glob ignore patterns
|
|
50
|
-
*/
|
|
51
|
-
private static regexToGlob(pattern: RegExp): string {
|
|
52
|
-
// Convert common patterns
|
|
53
|
-
const src = pattern.source;
|
|
54
|
-
if (src === "node_modules") return "**/node_modules/**";
|
|
55
|
-
if (src === String.raw`\.git`) return "**/.git/**";
|
|
56
|
-
if (src === String.raw`\.build`) return "**/.build/**";
|
|
57
|
-
if (src === String.raw`\.pio[/\\]build`) return "**/.pio/build/**";
|
|
58
|
-
// Fallback: wrap in wildcards
|
|
59
|
-
return `**/*${src.replaceAll("\\", "")}*/**`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
35
|
/**
|
|
63
36
|
* Classify a file path into a discovered file
|
|
64
37
|
*/
|
|
@@ -72,82 +45,6 @@ class FileDiscovery {
|
|
|
72
45
|
};
|
|
73
46
|
}
|
|
74
47
|
|
|
75
|
-
/**
|
|
76
|
-
* Discover files in the given directories
|
|
77
|
-
*
|
|
78
|
-
* Issue #331: Uses fast-glob's unique option to avoid duplicates
|
|
79
|
-
* when overlapping directories are provided.
|
|
80
|
-
*
|
|
81
|
-
* Note: fast-glob accesses the filesystem directly; the fs parameter
|
|
82
|
-
* is used only for directory existence checks.
|
|
83
|
-
*
|
|
84
|
-
* @param directories - Directories to scan
|
|
85
|
-
* @param options - Discovery options
|
|
86
|
-
* @param fs - File system abstraction (defaults to NodeFileSystem)
|
|
87
|
-
*/
|
|
88
|
-
static discover(
|
|
89
|
-
directories: string[],
|
|
90
|
-
options: IDiscoveryOptions = {},
|
|
91
|
-
fs: IFileSystem = defaultFs,
|
|
92
|
-
): IDiscoveredFile[] {
|
|
93
|
-
const recursive = options.recursive ?? true;
|
|
94
|
-
const extensions = options.extensions ?? Object.keys(EXTENSION_MAP);
|
|
95
|
-
|
|
96
|
-
// Build ignore patterns
|
|
97
|
-
let ignorePatterns: string[];
|
|
98
|
-
if (options.excludePatterns) {
|
|
99
|
-
ignorePatterns = options.excludePatterns.map((r) => this.regexToGlob(r));
|
|
100
|
-
} else {
|
|
101
|
-
ignorePatterns = DEFAULT_IGNORE_GLOBS;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Build glob pattern for extensions
|
|
105
|
-
const extPattern =
|
|
106
|
-
extensions.length === 1
|
|
107
|
-
? `*${extensions[0]}`
|
|
108
|
-
: `*{${extensions.join(",")}}`;
|
|
109
|
-
const pattern = recursive ? `**/${extPattern}` : extPattern;
|
|
110
|
-
|
|
111
|
-
const allFiles: IDiscoveredFile[] = [];
|
|
112
|
-
|
|
113
|
-
for (const dir of directories) {
|
|
114
|
-
const resolvedDir = resolve(dir);
|
|
115
|
-
|
|
116
|
-
if (!fs.exists(resolvedDir)) {
|
|
117
|
-
console.warn(`Warning: Directory not found: ${dir}`);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
// Use fast-glob to find files
|
|
123
|
-
const files = fg.sync(pattern, {
|
|
124
|
-
cwd: resolvedDir,
|
|
125
|
-
absolute: true,
|
|
126
|
-
ignore: ignorePatterns,
|
|
127
|
-
deep: recursive ? Infinity : 1,
|
|
128
|
-
onlyFiles: true,
|
|
129
|
-
followSymbolicLinks: false,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
for (const file of files) {
|
|
133
|
-
allFiles.push(this.classifyFile(file));
|
|
134
|
-
}
|
|
135
|
-
} catch {
|
|
136
|
-
console.warn(`Warning: Cannot read directory: ${dir}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Issue #331: Remove duplicates from overlapping directories
|
|
141
|
-
const seenPaths = new Set<string>();
|
|
142
|
-
return allFiles.filter((file) => {
|
|
143
|
-
if (seenPaths.has(file.path)) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
seenPaths.add(file.path);
|
|
147
|
-
return true;
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
48
|
/**
|
|
152
49
|
* Discover a single file
|
|
153
50
|
*
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { resolve, extname, basename } from "node:path";
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Input expansion for C-Next CLI
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* Handles recursive directory scanning and file validation.
|
|
7
|
+
* Validates and resolves file paths for compilation.
|
|
9
8
|
*/
|
|
10
9
|
class InputExpansion {
|
|
10
|
+
private static readonly CPP_EXTENSIONS = [
|
|
11
|
+
".c",
|
|
12
|
+
".cpp",
|
|
13
|
+
".cc",
|
|
14
|
+
".cxx",
|
|
15
|
+
".c++",
|
|
16
|
+
];
|
|
17
|
+
private static readonly CNEXT_EXTENSIONS = [".cnx", ".cnext"];
|
|
11
18
|
/**
|
|
12
|
-
* Expand inputs (files
|
|
19
|
+
* Expand inputs (files) to list of .cnx files
|
|
13
20
|
*
|
|
14
|
-
* @param inputs - Array of file paths
|
|
21
|
+
* @param inputs - Array of file paths
|
|
15
22
|
* @returns Array of .cnx file paths
|
|
16
23
|
*/
|
|
17
24
|
static expandInputs(inputs: string[]): string[] {
|
|
@@ -24,75 +31,17 @@ class InputExpansion {
|
|
|
24
31
|
throw new Error(`Input not found: ${input}`);
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (stats.isDirectory()) {
|
|
30
|
-
// Recursively find .cnx files
|
|
31
|
-
const cnextFiles = this.findCNextFiles(resolvedPath);
|
|
32
|
-
files.push(...cnextFiles);
|
|
33
|
-
} else if (stats.isFile()) {
|
|
34
|
-
// Validate and add file
|
|
35
|
-
this.validateFileExtension(resolvedPath);
|
|
36
|
-
files.push(resolvedPath);
|
|
37
|
-
}
|
|
34
|
+
this.validateFileExtension(resolvedPath);
|
|
35
|
+
files.push(resolvedPath);
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
// Remove duplicates
|
|
41
38
|
return Array.from(new Set(files));
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
/**
|
|
45
|
-
* Recursively find .cnx files in directory
|
|
46
|
-
*
|
|
47
|
-
* @param dir - Directory to scan
|
|
48
|
-
* @returns Array of .cnx file paths
|
|
49
|
-
*/
|
|
50
|
-
static findCNextFiles(dir: string): string[] {
|
|
51
|
-
const files: string[] = [];
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
55
|
-
|
|
56
|
-
for (const entry of entries) {
|
|
57
|
-
const fullPath = resolve(dir, entry.name);
|
|
58
|
-
|
|
59
|
-
// Skip hidden directories and common build/dependency directories
|
|
60
|
-
if (entry.isDirectory()) {
|
|
61
|
-
const dirName = entry.name;
|
|
62
|
-
if (
|
|
63
|
-
dirName.startsWith(".") ||
|
|
64
|
-
dirName === "node_modules" ||
|
|
65
|
-
dirName === "build" ||
|
|
66
|
-
dirName === ".pio" ||
|
|
67
|
-
dirName === "dist"
|
|
68
|
-
) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Recursively scan subdirectory
|
|
73
|
-
const subFiles = this.findCNextFiles(fullPath);
|
|
74
|
-
files.push(...subFiles);
|
|
75
|
-
} else if (entry.isFile()) {
|
|
76
|
-
const ext = extname(entry.name);
|
|
77
|
-
if (ext === ".cnx" || ext === ".cnext") {
|
|
78
|
-
files.push(fullPath);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
throw new Error(`Failed to scan directory ${dir}: ${error}`, {
|
|
84
|
-
cause: error,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return files;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
41
|
/**
|
|
92
42
|
* Validate file extension
|
|
93
43
|
*
|
|
94
|
-
* Accepts: .cnx, .cnext
|
|
95
|
-
* Rejects: .c, .cpp, .cc, .cxx, .c++ (implementation files)
|
|
44
|
+
* Accepts: .cnx, .cnext, .c, .cpp, .cc, .cxx, .c++
|
|
96
45
|
*
|
|
97
46
|
* @param path - File path to validate
|
|
98
47
|
* @throws Error if extension is invalid
|
|
@@ -101,24 +50,31 @@ class InputExpansion {
|
|
|
101
50
|
const ext = extname(path);
|
|
102
51
|
const fileName = basename(path);
|
|
103
52
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
throw new Error(
|
|
108
|
-
`Cannot process implementation file '${fileName}'. ` +
|
|
109
|
-
`C-Next only compiles .cnx files. ` +
|
|
110
|
-
`If you need to include this file, create a header (.h) instead.`,
|
|
111
|
-
);
|
|
53
|
+
// Accept C-Next source files
|
|
54
|
+
if (InputExpansion.CNEXT_EXTENSIONS.includes(ext)) {
|
|
55
|
+
return;
|
|
112
56
|
}
|
|
113
57
|
|
|
114
|
-
// Accept C
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
throw new Error(
|
|
118
|
-
`Invalid file extension '${ext}' for file '${fileName}'. ` +
|
|
119
|
-
`C-Next only accepts .cnx or .cnext files.`,
|
|
120
|
-
);
|
|
58
|
+
// Accept C/C++ entry point files
|
|
59
|
+
if (InputExpansion.CPP_EXTENSIONS.includes(ext)) {
|
|
60
|
+
return;
|
|
121
61
|
}
|
|
62
|
+
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Invalid file extension '${ext}' for file '${fileName}'. ` +
|
|
65
|
+
`C-Next only accepts .cnx, .cnext, .c, .cpp, .cc, .cxx, or .c++ files.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a file is a C/C++ entry point
|
|
71
|
+
*
|
|
72
|
+
* @param path - File path to check
|
|
73
|
+
* @returns true if the file has a C/C++ extension
|
|
74
|
+
*/
|
|
75
|
+
static isCppEntryPoint(path: string): boolean {
|
|
76
|
+
const ext = extname(path);
|
|
77
|
+
return InputExpansion.CPP_EXTENSIONS.includes(ext);
|
|
122
78
|
}
|
|
123
79
|
}
|
|
124
80
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import CNextMarkerDetector from "../CNextMarkerDetector";
|
|
3
|
+
|
|
4
|
+
describe("CNextMarkerDetector", () => {
|
|
5
|
+
describe("extractSourcePath", () => {
|
|
6
|
+
it("should extract source path from valid marker", () => {
|
|
7
|
+
const content = `/**
|
|
8
|
+
* Generated by C-Next Transpiler from: led.cnx
|
|
9
|
+
* Header file for cross-language interoperability
|
|
10
|
+
*/`;
|
|
11
|
+
expect(CNextMarkerDetector.extractSourcePath(content)).toBe("led.cnx");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should return null for header without marker", () => {
|
|
15
|
+
const content = `/**
|
|
16
|
+
* Some other header
|
|
17
|
+
*/
|
|
18
|
+
#ifndef FOO_H
|
|
19
|
+
#define FOO_H`;
|
|
20
|
+
expect(CNextMarkerDetector.extractSourcePath(content)).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should return null for old-style marker without source path", () => {
|
|
24
|
+
const content = `/**
|
|
25
|
+
* Generated by C-Next Transpiler
|
|
26
|
+
* Header file for cross-language interoperability
|
|
27
|
+
*/`;
|
|
28
|
+
expect(CNextMarkerDetector.extractSourcePath(content)).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should handle paths with directories", () => {
|
|
32
|
+
const content = `/**
|
|
33
|
+
* Generated by C-Next Transpiler from: ../lib/utils.cnx
|
|
34
|
+
*/`;
|
|
35
|
+
expect(CNextMarkerDetector.extractSourcePath(content)).toBe(
|
|
36
|
+
"../lib/utils.cnx",
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("isCNextGenerated", () => {
|
|
42
|
+
it("should return true for C-Next generated header", () => {
|
|
43
|
+
const content = `/**
|
|
44
|
+
* Generated by C-Next Transpiler from: led.cnx
|
|
45
|
+
*/`;
|
|
46
|
+
expect(CNextMarkerDetector.isCNextGenerated(content)).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should return true for old-style marker", () => {
|
|
50
|
+
const content = `/**
|
|
51
|
+
* Generated by C-Next Transpiler
|
|
52
|
+
*/`;
|
|
53
|
+
expect(CNextMarkerDetector.isCNextGenerated(content)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should return false for non-generated header", () => {
|
|
57
|
+
const content = `#ifndef FOO_H
|
|
58
|
+
#define FOO_H`;
|
|
59
|
+
expect(CNextMarkerDetector.isCNextGenerated(content)).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|