c-next 0.2.1 → 0.2.2
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/bin/cnext.js +17 -7
- package/dist/index.js +139747 -0
- package/dist/index.js.map +7 -0
- package/package.json +8 -4
- package/src/cli/Cli.ts +5 -2
- package/src/cli/PathNormalizer.ts +170 -0
- package/src/cli/PlatformIOCommand.ts +51 -3
- package/src/cli/__tests__/Cli.integration.test.ts +100 -0
- package/src/cli/__tests__/Cli.test.ts +17 -12
- package/src/cli/__tests__/PathNormalizer.test.ts +411 -0
- package/src/cli/__tests__/PlatformIOCommand.test.ts +156 -0
- package/src/cli/serve/__tests__/ServeCommand.test.ts +1 -1
- package/src/transpiler/NodeFileSystem.ts +5 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +17 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +27 -32
- package/src/transpiler/output/codegen/TypeResolver.ts +12 -24
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +130 -5
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +67 -57
- package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +9 -13
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +20 -10
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +5 -2
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +18 -0
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +25 -4
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +18 -0
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +51 -2
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +76 -8
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +147 -0
- package/src/transpiler/output/codegen/generators/expressions/__tests__/LiteralGenerator.test.ts +116 -0
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +6 -5
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +14 -5
- package/src/transpiler/types/IFileSystem.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"packageManager": "npm@11.9.0",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"test:q": "tsx scripts/test.ts -q",
|
|
24
24
|
"test:update": "tsx scripts/test.ts --update",
|
|
25
25
|
"analyze": "./scripts/static-analysis.sh",
|
|
26
|
-
"clean": "rm -rf src/transpiler/logic/parser/grammar src/transpiler/logic/parser/c/grammar src/transpiler/logic/parser/cpp/grammar",
|
|
26
|
+
"clean": "rm -rf dist src/transpiler/logic/parser/grammar src/transpiler/logic/parser/c/grammar src/transpiler/logic/parser/cpp/grammar",
|
|
27
27
|
"prettier:check": "prettier --check .",
|
|
28
28
|
"prettier:fix": "prettier --write .",
|
|
29
29
|
"cspell:check": "cspell .",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"depcruise": "depcruise src/transpiler --config .dependency-cruiser.cjs",
|
|
34
34
|
"depcruise:graph": "depcruise src/transpiler --config .dependency-cruiser.cjs --output-type dot | dot -T svg > dependency-graph.svg",
|
|
35
35
|
"prepare": "husky",
|
|
36
|
-
"prepublishOnly": "npm run prettier:check && npm run oxlint:check && npm test",
|
|
36
|
+
"prepublishOnly": "npm run prettier:check && npm run oxlint:check && npm run build && npm test",
|
|
37
37
|
"coverage:check": "tsx scripts/coverage-checker.ts check",
|
|
38
38
|
"coverage:report": "tsx scripts/coverage-checker.ts report",
|
|
39
39
|
"coverage:gaps": "tsx scripts/coverage-checker.ts gaps",
|
|
@@ -45,7 +45,9 @@
|
|
|
45
45
|
"unit:watch": "vitest",
|
|
46
46
|
"unit:coverage": "vitest run --coverage",
|
|
47
47
|
"unit:coverage:html": "vitest run --coverage && echo 'Coverage report: coverage/index.html'",
|
|
48
|
-
"
|
|
48
|
+
"build": "node scripts/build.mjs",
|
|
49
|
+
"test:all": "npm run build && npm run unit && npm run test:q && npm run validate:c",
|
|
50
|
+
"validate:c": "node scripts/batch-validate.mjs",
|
|
49
51
|
"duplication": "npx jscpd src/ scripts/ --reporters console",
|
|
50
52
|
"duplication:json": "npx jscpd src/ scripts/ --reporters json --output .jscpd",
|
|
51
53
|
"duplication:sonar": "curl -s 'https://sonarcloud.io/api/measures/component_tree?component=jlaustill_c-next&metricKeys=duplicated_blocks,duplicated_lines&s=metric&metricSort=duplicated_blocks&metricSortFilter=withMeasuresOnly&asc=false&ps=20' | jq -r '.components[] | \"\\(.path)\\t\\(.measures[0].value) blocks\\t\\(.measures[1].value) lines\"'",
|
|
@@ -79,6 +81,7 @@
|
|
|
79
81
|
},
|
|
80
82
|
"files": [
|
|
81
83
|
"bin/",
|
|
84
|
+
"dist/",
|
|
82
85
|
"src/",
|
|
83
86
|
"grammar/",
|
|
84
87
|
"README.md",
|
|
@@ -87,6 +90,7 @@
|
|
|
87
90
|
"devDependencies": {
|
|
88
91
|
"@types/node": "^25.2.1",
|
|
89
92
|
"@types/yargs": "^17.0.35",
|
|
93
|
+
"esbuild": "^0.27.3",
|
|
90
94
|
"@vitest/coverage-v8": "^4.0.18",
|
|
91
95
|
"antlr4ng-cli": "^2.0.0",
|
|
92
96
|
"chalk": "^5.6.2",
|
package/src/cli/Cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import ConfigLoader from "./ConfigLoader";
|
|
|
10
10
|
import ConfigPrinter from "./ConfigPrinter";
|
|
11
11
|
import PlatformIOCommand from "./PlatformIOCommand";
|
|
12
12
|
import CleanCommand from "./CleanCommand";
|
|
13
|
+
import PathNormalizer from "./PathNormalizer";
|
|
13
14
|
import ICliResult from "./types/ICliResult";
|
|
14
15
|
import ICliConfig from "./types/ICliConfig";
|
|
15
16
|
import IParsedArgs from "./types/IParsedArgs";
|
|
@@ -92,7 +93,7 @@ class Cli {
|
|
|
92
93
|
args: IParsedArgs,
|
|
93
94
|
fileConfig: IFileConfig,
|
|
94
95
|
): ICliConfig {
|
|
95
|
-
|
|
96
|
+
const rawConfig: ICliConfig = {
|
|
96
97
|
inputs: args.inputFiles,
|
|
97
98
|
outputPath: args.outputPath || fileConfig.output || "",
|
|
98
99
|
// Merge include dirs: config includes come first, CLI includes override/append
|
|
@@ -100,7 +101,7 @@ class Cli {
|
|
|
100
101
|
defines: args.defines,
|
|
101
102
|
preprocess: args.preprocess,
|
|
102
103
|
verbose: args.verbose,
|
|
103
|
-
cppRequired: args.cppRequired
|
|
104
|
+
cppRequired: args.cppRequired || fileConfig.cppRequired || false,
|
|
104
105
|
noCache: args.noCache || fileConfig.noCache === true,
|
|
105
106
|
parseOnly: args.parseOnly,
|
|
106
107
|
headerOutDir: args.headerOutDir ?? fileConfig.headerOut,
|
|
@@ -108,6 +109,8 @@ class Cli {
|
|
|
108
109
|
target: args.target ?? fileConfig.target,
|
|
109
110
|
debugMode: args.debugMode || fileConfig.debugMode,
|
|
110
111
|
};
|
|
112
|
+
|
|
113
|
+
return PathNormalizer.normalizeConfig(rawConfig);
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PathNormalizer
|
|
3
|
+
* Centralized path normalization for all config paths.
|
|
4
|
+
* Handles tilde expansion and recursive directory search.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import IFileSystem from "../transpiler/types/IFileSystem";
|
|
9
|
+
import NodeFileSystem from "../transpiler/NodeFileSystem";
|
|
10
|
+
import ICliConfig from "./types/ICliConfig";
|
|
11
|
+
|
|
12
|
+
/** Default file system instance */
|
|
13
|
+
const defaultFs = NodeFileSystem.instance;
|
|
14
|
+
|
|
15
|
+
class PathNormalizer {
|
|
16
|
+
/**
|
|
17
|
+
* Expand ~ at the start of a path to the home directory.
|
|
18
|
+
* Only expands leading tilde (~/path or bare ~).
|
|
19
|
+
* @param path - Path that may start with ~
|
|
20
|
+
* @returns Path with ~ expanded to home directory
|
|
21
|
+
*/
|
|
22
|
+
static expandTilde(path: string): string {
|
|
23
|
+
if (!path.startsWith("~")) {
|
|
24
|
+
return path;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
28
|
+
if (!home) {
|
|
29
|
+
return path;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (path === "~") {
|
|
33
|
+
return home;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (path.startsWith("~/")) {
|
|
37
|
+
return home + path.slice(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return path;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Expand path/** to include all subdirectories recursively.
|
|
45
|
+
* If path doesn't end with /**, returns the path as single-element array
|
|
46
|
+
* (if it exists) or empty array (if it doesn't exist).
|
|
47
|
+
* @param path - Path that may end with /**
|
|
48
|
+
* @param fs - File system abstraction for testing
|
|
49
|
+
* @returns Array of all directories found
|
|
50
|
+
*/
|
|
51
|
+
static expandRecursive(path: string, fs: IFileSystem = defaultFs): string[] {
|
|
52
|
+
const hasRecursiveSuffix = path.endsWith("/**");
|
|
53
|
+
const basePath = hasRecursiveSuffix ? path.slice(0, -3) : path;
|
|
54
|
+
|
|
55
|
+
if (!fs.exists(basePath)) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!fs.isDirectory(basePath)) {
|
|
60
|
+
return [basePath];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!hasRecursiveSuffix) {
|
|
64
|
+
return [basePath];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Recursively collect all subdirectories
|
|
68
|
+
const dirs: string[] = [basePath];
|
|
69
|
+
this.collectSubdirectories(basePath, dirs, fs);
|
|
70
|
+
return dirs;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Recursively collect all subdirectories into the dirs array.
|
|
75
|
+
* Uses a visited set to prevent symlink loops.
|
|
76
|
+
*/
|
|
77
|
+
private static collectSubdirectories(
|
|
78
|
+
dir: string,
|
|
79
|
+
dirs: string[],
|
|
80
|
+
fs: IFileSystem,
|
|
81
|
+
visited: Set<string> = new Set(),
|
|
82
|
+
): void {
|
|
83
|
+
// Get real path to detect symlink loops
|
|
84
|
+
const realPath = fs.realpath ? fs.realpath(dir) : dir;
|
|
85
|
+
if (visited.has(realPath)) {
|
|
86
|
+
return; // Skip already-visited directories (symlink loop protection)
|
|
87
|
+
}
|
|
88
|
+
visited.add(realPath);
|
|
89
|
+
|
|
90
|
+
const entries = fs.readdir(dir);
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
const fullPath = join(dir, entry);
|
|
93
|
+
if (fs.isDirectory(fullPath)) {
|
|
94
|
+
// Check if this subdirectory's real path was already visited
|
|
95
|
+
const subRealPath = fs.realpath ? fs.realpath(fullPath) : fullPath;
|
|
96
|
+
if (!visited.has(subRealPath)) {
|
|
97
|
+
dirs.push(fullPath);
|
|
98
|
+
this.collectSubdirectories(fullPath, dirs, fs, visited);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Normalize a single path (tilde expansion only).
|
|
106
|
+
* Used for output, headerOut, basePath.
|
|
107
|
+
* @param path - Path to normalize
|
|
108
|
+
* @returns Normalized path
|
|
109
|
+
*/
|
|
110
|
+
static normalizePath(path: string): string {
|
|
111
|
+
if (!path) {
|
|
112
|
+
return path;
|
|
113
|
+
}
|
|
114
|
+
return this.expandTilde(path);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Normalize include paths (tilde + recursive expansion).
|
|
119
|
+
* Deduplicates paths to avoid redundant includes.
|
|
120
|
+
* @param paths - Array of paths to normalize
|
|
121
|
+
* @param fs - File system abstraction for testing
|
|
122
|
+
* @returns Flattened array of all resolved directories (deduplicated)
|
|
123
|
+
*/
|
|
124
|
+
static normalizeIncludePaths(
|
|
125
|
+
paths: string[],
|
|
126
|
+
fs: IFileSystem = defaultFs,
|
|
127
|
+
): string[] {
|
|
128
|
+
const seen = new Set<string>();
|
|
129
|
+
const result: string[] = [];
|
|
130
|
+
|
|
131
|
+
for (const path of paths) {
|
|
132
|
+
const expanded = this.expandTilde(path);
|
|
133
|
+
const dirs = this.expandRecursive(expanded, fs);
|
|
134
|
+
for (const dir of dirs) {
|
|
135
|
+
if (!seen.has(dir)) {
|
|
136
|
+
seen.add(dir);
|
|
137
|
+
result.push(dir);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Normalize all paths in a CLI config.
|
|
147
|
+
* Single entry point for all path normalization.
|
|
148
|
+
* @param config - CLI config with potentially unnormalized paths
|
|
149
|
+
* @param fs - File system abstraction for testing
|
|
150
|
+
* @returns New config with all paths normalized
|
|
151
|
+
*/
|
|
152
|
+
static normalizeConfig(
|
|
153
|
+
config: ICliConfig,
|
|
154
|
+
fs: IFileSystem = defaultFs,
|
|
155
|
+
): ICliConfig {
|
|
156
|
+
return {
|
|
157
|
+
...config,
|
|
158
|
+
outputPath: this.normalizePath(config.outputPath),
|
|
159
|
+
headerOutDir: config.headerOutDir
|
|
160
|
+
? this.normalizePath(config.headerOutDir)
|
|
161
|
+
: undefined,
|
|
162
|
+
basePath: config.basePath
|
|
163
|
+
? this.normalizePath(config.basePath)
|
|
164
|
+
: undefined,
|
|
165
|
+
includeDirs: this.normalizeIncludePaths(config.includeDirs, fs),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default PathNormalizer;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
8
|
+
import IFileConfig from "./types/IFileConfig";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Resolved paths for PlatformIO project.
|
|
@@ -43,11 +44,14 @@ class PlatformIOCommand {
|
|
|
43
44
|
const { pioIniPath, scriptPath } = getPioProjectPaths();
|
|
44
45
|
|
|
45
46
|
// Create cnext_build.py script
|
|
47
|
+
// Issue #833: Run transpilation at import time (before compilation),
|
|
48
|
+
// not as a pre-action on buildprog (which runs after compilation)
|
|
46
49
|
const buildScript = `Import("env")
|
|
47
50
|
import subprocess
|
|
51
|
+
import sys
|
|
48
52
|
from pathlib import Path
|
|
49
53
|
|
|
50
|
-
def transpile_cnext(
|
|
54
|
+
def transpile_cnext():
|
|
51
55
|
"""Transpile all .cnx files before build"""
|
|
52
56
|
# Find all .cnx files in src directory
|
|
53
57
|
src_dir = Path("src")
|
|
@@ -72,9 +76,10 @@ def transpile_cnext(source, target, env):
|
|
|
72
76
|
except subprocess.CalledProcessError as e:
|
|
73
77
|
print(f" ✗ Error: {cnx_file.name}")
|
|
74
78
|
print(e.stderr)
|
|
75
|
-
|
|
79
|
+
sys.exit(1)
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
# Run transpilation at import time (before compilation starts)
|
|
82
|
+
transpile_cnext()
|
|
78
83
|
`;
|
|
79
84
|
|
|
80
85
|
writeFileSync(scriptPath, buildScript, "utf-8");
|
|
@@ -107,6 +112,9 @@ env.AddPreAction("buildprog", transpile_cnext)
|
|
|
107
112
|
writeFileSync(pioIniPath, pioIni, "utf-8");
|
|
108
113
|
console.log(`✓ Modified: ${pioIniPath}`);
|
|
109
114
|
|
|
115
|
+
// Setup cnext.config.json for PlatformIO
|
|
116
|
+
this.setupConfig();
|
|
117
|
+
|
|
110
118
|
console.log("");
|
|
111
119
|
console.log("✓ PlatformIO integration configured!");
|
|
112
120
|
console.log("");
|
|
@@ -122,6 +130,46 @@ env.AddPreAction("buildprog", transpile_cnext)
|
|
|
122
130
|
console.log("Commit both .cnx and generated .c files to version control.");
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Setup cnext.config.json with PlatformIO-appropriate defaults
|
|
135
|
+
* - Appends .pio/libdeps to include array
|
|
136
|
+
* - Sets headerOut to "include" if not already set
|
|
137
|
+
*/
|
|
138
|
+
private static setupConfig(): void {
|
|
139
|
+
const configPath = resolve(process.cwd(), "cnext.config.json");
|
|
140
|
+
const pioInclude = ".pio/libdeps";
|
|
141
|
+
|
|
142
|
+
let config: IFileConfig = {};
|
|
143
|
+
|
|
144
|
+
if (existsSync(configPath)) {
|
|
145
|
+
try {
|
|
146
|
+
const content = readFileSync(configPath, "utf-8");
|
|
147
|
+
config = JSON.parse(content) as IFileConfig;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
150
|
+
console.log(
|
|
151
|
+
`⚠ Could not parse existing cnext.config.json (${msg}), creating new one`,
|
|
152
|
+
);
|
|
153
|
+
config = {};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Append .pio/libdeps to include array if not already present
|
|
158
|
+
const includes = config.include ?? [];
|
|
159
|
+
if (!includes.includes(pioInclude)) {
|
|
160
|
+
config.include = [...includes, pioInclude];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Set headerOut only if not already set
|
|
164
|
+
if (!config.headerOut) {
|
|
165
|
+
config.headerOut = "include";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Write config (with pretty formatting)
|
|
169
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
170
|
+
console.log(`✓ Updated: ${configPath}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
125
173
|
/**
|
|
126
174
|
* Remove PlatformIO integration
|
|
127
175
|
* Deletes cnext_build.py and removes extra_scripts from platformio.ini
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for Cli path normalization.
|
|
3
|
+
* These tests do NOT mock dependencies - they test real behavior.
|
|
4
|
+
*
|
|
5
|
+
* Separate from Cli.test.ts to avoid vi.mock() hoisting issues.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import Cli from "../Cli";
|
|
13
|
+
|
|
14
|
+
describe("Cli path normalization (integration)", () => {
|
|
15
|
+
let tempDir: string;
|
|
16
|
+
const originalHome = process.env.HOME;
|
|
17
|
+
const originalArgv = process.argv;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
tempDir = mkdtempSync(join(tmpdir(), "cli-paths-"));
|
|
21
|
+
process.env.HOME = "/home/testuser";
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
26
|
+
process.env.HOME = originalHome;
|
|
27
|
+
process.argv = originalArgv;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("expands tilde in config include paths", () => {
|
|
31
|
+
// Create a real home directory structure to verify tilde expansion
|
|
32
|
+
const homeDir = mkdtempSync(join(tmpdir(), "home-"));
|
|
33
|
+
mkdirSync(join(homeDir, "sdk", "include"), { recursive: true });
|
|
34
|
+
|
|
35
|
+
process.env.HOME = homeDir;
|
|
36
|
+
|
|
37
|
+
writeFileSync(
|
|
38
|
+
join(tempDir, "cnext.config.json"),
|
|
39
|
+
JSON.stringify({ include: ["~/sdk/include"] }),
|
|
40
|
+
);
|
|
41
|
+
writeFileSync(join(tempDir, "test.cnx"), "void main() {}");
|
|
42
|
+
|
|
43
|
+
process.argv = ["node", "cnext", join(tempDir, "test.cnx")];
|
|
44
|
+
|
|
45
|
+
const result = Cli.run();
|
|
46
|
+
|
|
47
|
+
// Verify tilde was expanded to actual home directory path
|
|
48
|
+
expect(result.config?.includeDirs).toContain(join(homeDir, "sdk/include"));
|
|
49
|
+
// Verify the unexpanded tilde path is NOT present
|
|
50
|
+
expect(result.config?.includeDirs).not.toContain("~/sdk/include");
|
|
51
|
+
|
|
52
|
+
rmSync(homeDir, { recursive: true, force: true });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("expands ** in config include paths", () => {
|
|
56
|
+
// Create directory structure
|
|
57
|
+
mkdirSync(join(tempDir, "include", "sub"), { recursive: true });
|
|
58
|
+
writeFileSync(
|
|
59
|
+
join(tempDir, "cnext.config.json"),
|
|
60
|
+
JSON.stringify({ include: [`${tempDir}/include/**`] }),
|
|
61
|
+
);
|
|
62
|
+
writeFileSync(join(tempDir, "test.cnx"), "void main() {}");
|
|
63
|
+
|
|
64
|
+
process.argv = ["node", "cnext", join(tempDir, "test.cnx")];
|
|
65
|
+
|
|
66
|
+
const result = Cli.run();
|
|
67
|
+
|
|
68
|
+
expect(result.config?.includeDirs).toContain(join(tempDir, "include"));
|
|
69
|
+
expect(result.config?.includeDirs).toContain(
|
|
70
|
+
join(tempDir, "include", "sub"),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("expands tilde in CLI --include paths", () => {
|
|
75
|
+
// Create a real home directory structure to verify tilde expansion
|
|
76
|
+
const homeDir = mkdtempSync(join(tmpdir(), "home-"));
|
|
77
|
+
mkdirSync(join(homeDir, "my-libs"), { recursive: true });
|
|
78
|
+
|
|
79
|
+
process.env.HOME = homeDir;
|
|
80
|
+
|
|
81
|
+
writeFileSync(join(tempDir, "test.cnx"), "void main() {}");
|
|
82
|
+
|
|
83
|
+
process.argv = [
|
|
84
|
+
"node",
|
|
85
|
+
"cnext",
|
|
86
|
+
join(tempDir, "test.cnx"),
|
|
87
|
+
"--include",
|
|
88
|
+
"~/my-libs",
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const result = Cli.run();
|
|
92
|
+
|
|
93
|
+
// Verify tilde was expanded to actual home directory path
|
|
94
|
+
expect(result.config?.includeDirs).toContain(join(homeDir, "my-libs"));
|
|
95
|
+
// Verify the unexpanded tilde path is NOT present
|
|
96
|
+
expect(result.config?.includeDirs).not.toContain("~/my-libs");
|
|
97
|
+
|
|
98
|
+
rmSync(homeDir, { recursive: true, force: true });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -25,6 +25,12 @@ vi.mock("../ConfigLoader");
|
|
|
25
25
|
vi.mock("../ConfigPrinter");
|
|
26
26
|
vi.mock("../PlatformIOCommand");
|
|
27
27
|
vi.mock("../CleanCommand");
|
|
28
|
+
vi.mock("../PathNormalizer", () => ({
|
|
29
|
+
default: {
|
|
30
|
+
// Pass through the config unchanged for unit tests
|
|
31
|
+
normalizeConfig: (config: unknown) => config,
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
28
34
|
|
|
29
35
|
describe("Cli", () => {
|
|
30
36
|
let mockParsedArgs: IParsedArgs;
|
|
@@ -180,40 +186,39 @@ describe("Cli", () => {
|
|
|
180
186
|
expect(result.config?.includeDirs).toContain("lib/");
|
|
181
187
|
});
|
|
182
188
|
|
|
183
|
-
it("
|
|
184
|
-
//
|
|
185
|
-
//
|
|
189
|
+
it("honors config file cppRequired when CLI does not specify --cpp (issue #827)", () => {
|
|
190
|
+
// Issue #827: When user doesn't pass --cpp, config file cppRequired should be used
|
|
191
|
+
// yargs returns cppRequired: false as the default, but config file should override
|
|
186
192
|
const fileConfig: IFileConfig = {
|
|
187
193
|
cppRequired: true,
|
|
188
194
|
};
|
|
189
195
|
vi.mocked(ConfigLoader.load).mockReturnValue(fileConfig);
|
|
190
196
|
|
|
191
|
-
//
|
|
192
|
-
mockParsedArgs.cppRequired =
|
|
197
|
+
// CLI has cppRequired: false (yargs default when --cpp not specified)
|
|
198
|
+
mockParsedArgs.cppRequired = false;
|
|
193
199
|
vi.mocked(ArgParser.parse).mockReturnValue(mockParsedArgs);
|
|
194
200
|
|
|
195
201
|
const result = Cli.run();
|
|
196
202
|
|
|
203
|
+
// Config file's cppRequired: true should be honored
|
|
197
204
|
expect(result.config?.cppRequired).toBe(true);
|
|
198
205
|
});
|
|
199
206
|
|
|
200
|
-
it("CLI
|
|
201
|
-
mockParsedArgs.cppRequired =
|
|
207
|
+
it("CLI --cpp flag takes precedence over file config", () => {
|
|
208
|
+
mockParsedArgs.cppRequired = true;
|
|
202
209
|
mockParsedArgs.target = "cortex-m0";
|
|
203
210
|
vi.mocked(ArgParser.parse).mockReturnValue(mockParsedArgs);
|
|
204
211
|
|
|
205
212
|
const fileConfig: IFileConfig = {
|
|
206
|
-
cppRequired:
|
|
213
|
+
cppRequired: false,
|
|
207
214
|
target: "teensy41",
|
|
208
215
|
};
|
|
209
216
|
vi.mocked(ConfigLoader.load).mockReturnValue(fileConfig);
|
|
210
217
|
|
|
211
218
|
const result = Cli.run();
|
|
212
219
|
|
|
213
|
-
// CLI
|
|
214
|
-
|
|
215
|
-
// So if args.cppRequired is false (not undefined), it should use false
|
|
216
|
-
expect(result.config?.cppRequired).toBe(false);
|
|
220
|
+
// CLI --cpp flag should take precedence
|
|
221
|
+
expect(result.config?.cppRequired).toBe(true);
|
|
217
222
|
expect(result.config?.target).toBe("cortex-m0");
|
|
218
223
|
});
|
|
219
224
|
|