c-next 0.1.24 → 0.1.26
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/package.json +1 -1
- package/src/codegen/CodeGenerator.ts +22 -3
- package/src/codegen/HeaderGenerator.ts +3 -1
- package/src/codegen/generators/support/IncludeGenerator.ts +78 -2
- package/src/codegen/types/ICodeGeneratorOptions.ts +16 -0
- package/src/index.ts +26 -1
- package/src/pipeline/Pipeline.ts +54 -38
- package/src/project/Project.ts +1 -0
- package/src/project/types/IProjectConfig.ts +3 -0
- package/src/symbols/CppSymbolCollector.ts +41 -2
package/package.json
CHANGED
|
@@ -255,6 +255,12 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
255
255
|
/** ADR-010: Source file path for validating includes */
|
|
256
256
|
private sourcePath: string | null = null;
|
|
257
257
|
|
|
258
|
+
/** Issue #349: Include directories for resolving angle-bracket .cnx includes */
|
|
259
|
+
private includeDirs: string[] = [];
|
|
260
|
+
|
|
261
|
+
/** Issue #349: Input directories for calculating relative paths */
|
|
262
|
+
private inputs: string[] = [];
|
|
263
|
+
|
|
258
264
|
/** Token stream for comment extraction (ADR-043) */
|
|
259
265
|
private tokenStream: CommonTokenStream | null = null;
|
|
260
266
|
|
|
@@ -1309,6 +1315,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1309
1315
|
// ADR-010: Store source path for include validation
|
|
1310
1316
|
this.sourcePath = options?.sourcePath ?? null;
|
|
1311
1317
|
|
|
1318
|
+
// Issue #349: Store include directories and inputs for angle-bracket include resolution
|
|
1319
|
+
this.includeDirs = options?.includeDirs ?? [];
|
|
1320
|
+
this.inputs = options?.inputs ?? [];
|
|
1321
|
+
|
|
1312
1322
|
// Issue #250: Store C++ mode for temp variable generation
|
|
1313
1323
|
this.cppMode = options?.cppMode ?? false;
|
|
1314
1324
|
// Reset temp var state for each generation
|
|
@@ -1430,13 +1440,17 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1430
1440
|
// Issue #230: Self-include for extern "C" linkage
|
|
1431
1441
|
// When file has public symbols and headers are being generated,
|
|
1432
1442
|
// include own header to ensure proper C linkage
|
|
1443
|
+
// Issue #339: Use relative path from source root when available
|
|
1433
1444
|
if (
|
|
1434
1445
|
options?.generateHeaders &&
|
|
1435
1446
|
this.symbols!.hasPublicSymbols() &&
|
|
1436
1447
|
this.sourcePath
|
|
1437
1448
|
) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1449
|
+
// Issue #339: Prefer sourceRelativePath for correct directory structure
|
|
1450
|
+
// Otherwise fall back to basename for backward compatibility
|
|
1451
|
+
const pathToUse =
|
|
1452
|
+
options.sourceRelativePath || this.sourcePath.replace(/^.*[\\/]/, "");
|
|
1453
|
+
const headerName = pathToUse.replace(/\.cnx$|\.cnext$/, ".h");
|
|
1440
1454
|
output.push(`#include "${headerName}"`);
|
|
1441
1455
|
output.push("");
|
|
1442
1456
|
}
|
|
@@ -1613,9 +1627,14 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1613
1627
|
/**
|
|
1614
1628
|
* ADR-010: Transform #include directives, converting .cnx to .h
|
|
1615
1629
|
* ADR-053 A5: Delegates to IncludeGenerator
|
|
1630
|
+
* Issue #349: Now passes includeDirs and inputs for angle-bracket resolution
|
|
1616
1631
|
*/
|
|
1617
1632
|
private transformIncludeDirective(includeText: string): string {
|
|
1618
|
-
return includeTransformIncludeDirective(includeText,
|
|
1633
|
+
return includeTransformIncludeDirective(includeText, {
|
|
1634
|
+
sourcePath: this.sourcePath,
|
|
1635
|
+
includeDirs: this.includeDirs,
|
|
1636
|
+
inputs: this.inputs,
|
|
1637
|
+
});
|
|
1619
1638
|
}
|
|
1620
1639
|
|
|
1621
1640
|
// Issue #63: validateIncludeNotImplementationFile moved to TypeValidator
|
|
@@ -268,7 +268,9 @@ class HeaderGenerator {
|
|
|
268
268
|
passByValueParams?: TPassByValueParams,
|
|
269
269
|
): string | null {
|
|
270
270
|
// Map return type from C-Next to C
|
|
271
|
-
|
|
271
|
+
// Special case: main() always returns int for C/C++ compatibility
|
|
272
|
+
const mappedType = sym.type ? mapType(sym.type) : "void";
|
|
273
|
+
const returnType = sym.name === "main" ? "int" : mappedType;
|
|
272
274
|
|
|
273
275
|
// Issue #269: Get pass-by-value parameter names for this function
|
|
274
276
|
const passByValueSet = passByValueParams?.get(sym.name);
|
|
@@ -6,22 +6,98 @@ import * as fs from "fs";
|
|
|
6
6
|
import * as path from "path";
|
|
7
7
|
import * as Parser from "../../../parser/grammar/CNextParser";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Issue #349: Options for include transformation
|
|
11
|
+
*/
|
|
12
|
+
interface IIncludeTransformOptions {
|
|
13
|
+
sourcePath: string | null;
|
|
14
|
+
includeDirs?: string[];
|
|
15
|
+
inputs?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Issue #349: Find a .cnx file in the given search paths.
|
|
20
|
+
* Returns the absolute path if found, null otherwise.
|
|
21
|
+
*/
|
|
22
|
+
const findCnxFile = (
|
|
23
|
+
filename: string,
|
|
24
|
+
searchPaths: string[],
|
|
25
|
+
): string | null => {
|
|
26
|
+
for (const searchPath of searchPaths) {
|
|
27
|
+
const cnxPath = path.resolve(searchPath, `${filename}.cnx`);
|
|
28
|
+
if (fs.existsSync(cnxPath)) {
|
|
29
|
+
return cnxPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Issue #349: Calculate the relative path from input directories.
|
|
37
|
+
* Returns the relative path (e.g., "Display/utils.cnx") or null if not found.
|
|
38
|
+
*/
|
|
39
|
+
const getRelativePathFromInputs = (
|
|
40
|
+
filePath: string,
|
|
41
|
+
inputs: string[],
|
|
42
|
+
): string | null => {
|
|
43
|
+
for (const input of inputs) {
|
|
44
|
+
const resolvedInput = path.resolve(input);
|
|
45
|
+
|
|
46
|
+
// Skip if input is a file (not a directory)
|
|
47
|
+
if (fs.existsSync(resolvedInput) && fs.statSync(resolvedInput).isFile()) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const relativePath = path.relative(resolvedInput, filePath);
|
|
52
|
+
|
|
53
|
+
// If relative path doesn't start with '..' it's under this input
|
|
54
|
+
if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
55
|
+
return relativePath;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
|
|
9
61
|
/**
|
|
10
62
|
* ADR-010: Transform #include directives, converting .cnx to .h
|
|
11
63
|
* Validates that .cnx files exist if sourcePath is available
|
|
12
64
|
* Supports both <file.cnx> and "file.cnx" forms
|
|
65
|
+
*
|
|
66
|
+
* Issue #349: For angle-bracket includes, resolves the correct output path
|
|
67
|
+
* by finding the .cnx file and calculating its relative path from inputs.
|
|
13
68
|
*/
|
|
14
69
|
const transformIncludeDirective = (
|
|
15
70
|
includeText: string,
|
|
16
|
-
|
|
71
|
+
options: IIncludeTransformOptions,
|
|
17
72
|
): string => {
|
|
73
|
+
const { sourcePath, includeDirs = [], inputs = [] } = options;
|
|
74
|
+
|
|
18
75
|
// Match: #include <file.cnx> or #include "file.cnx"
|
|
19
76
|
const angleMatch = includeText.match(/#\s*include\s*<([^>]+)\.cnx>/);
|
|
20
77
|
const quoteMatch = includeText.match(/#\s*include\s*"([^"]+)\.cnx"/);
|
|
21
78
|
|
|
22
79
|
if (angleMatch) {
|
|
23
80
|
const filename = angleMatch[1];
|
|
24
|
-
|
|
81
|
+
|
|
82
|
+
// Issue #349: Try to resolve the .cnx file to get correct output path
|
|
83
|
+
if (sourcePath) {
|
|
84
|
+
const sourceDir = path.dirname(sourcePath);
|
|
85
|
+
// Build search paths: source directory first, then include directories
|
|
86
|
+
const searchPaths = [sourceDir, ...includeDirs];
|
|
87
|
+
|
|
88
|
+
const foundPath = findCnxFile(filename, searchPaths);
|
|
89
|
+
if (foundPath && inputs.length > 0) {
|
|
90
|
+
// Calculate relative path from inputs for correct header path
|
|
91
|
+
const relativePath = getRelativePathFromInputs(foundPath, inputs);
|
|
92
|
+
if (relativePath) {
|
|
93
|
+
// Transform .cnx to .h
|
|
94
|
+
const headerPath = relativePath.replace(/\.cnx$/, ".h");
|
|
95
|
+
return includeText.replace(`<${filename}.cnx>`, `<${headerPath}>`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fallback: simple replacement (for external includes or when resolution fails)
|
|
25
101
|
return includeText.replace(`<${filename}.cnx>`, `<${filename}.h>`);
|
|
26
102
|
} else if (quoteMatch) {
|
|
27
103
|
const filepath = quoteMatch[1];
|
|
@@ -18,6 +18,22 @@ interface ICodeGeneratorOptions {
|
|
|
18
18
|
* Uses temporary variables instead of compound literals for rvalue pointer params.
|
|
19
19
|
*/
|
|
20
20
|
cppMode?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Issue #339: Relative path from source root to source file for self-include.
|
|
23
|
+
* When set, self-includes will use this relative path instead of just the basename.
|
|
24
|
+
* Example: "Display/Utils.cnx" -> #include "Display/Utils.h"
|
|
25
|
+
*/
|
|
26
|
+
sourceRelativePath?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Issue #349: Include directories for resolving angle-bracket .cnx includes.
|
|
29
|
+
* Used to search for .cnx files referenced in #include <file.cnx> directives.
|
|
30
|
+
*/
|
|
31
|
+
includeDirs?: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Issue #349: Input directories for calculating relative paths.
|
|
34
|
+
* Used to determine the correct output path prefix for headers.
|
|
35
|
+
*/
|
|
36
|
+
inputs?: string[];
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
export default ICodeGeneratorOptions;
|
package/src/index.ts
CHANGED
|
@@ -196,6 +196,7 @@ async function runUnifiedMode(
|
|
|
196
196
|
verbose: boolean,
|
|
197
197
|
cppRequired: boolean,
|
|
198
198
|
noCache: boolean,
|
|
199
|
+
parseOnly: boolean,
|
|
199
200
|
headerOutDir?: string,
|
|
200
201
|
): Promise<void> {
|
|
201
202
|
// Issue #337: Identify which inputs are directories (for structure preservation)
|
|
@@ -283,6 +284,7 @@ async function runUnifiedMode(
|
|
|
283
284
|
defines,
|
|
284
285
|
cppRequired,
|
|
285
286
|
noCache,
|
|
287
|
+
parseOnly,
|
|
286
288
|
});
|
|
287
289
|
|
|
288
290
|
// Step 5: Compile
|
|
@@ -494,6 +496,7 @@ async function main(): Promise<void> {
|
|
|
494
496
|
let preprocess = true;
|
|
495
497
|
let verbose = false;
|
|
496
498
|
let noCache = false;
|
|
499
|
+
let parseOnly = false;
|
|
497
500
|
let headerOutDir: string | undefined;
|
|
498
501
|
let cleanMode = false;
|
|
499
502
|
|
|
@@ -514,6 +517,8 @@ async function main(): Promise<void> {
|
|
|
514
517
|
preprocess = false;
|
|
515
518
|
} else if (arg === "--no-cache") {
|
|
516
519
|
noCache = true;
|
|
520
|
+
} else if (arg === "--parse") {
|
|
521
|
+
parseOnly = true;
|
|
517
522
|
} else if (arg === "--header-out" && i + 1 < args.length) {
|
|
518
523
|
headerOutDir = args[++i];
|
|
519
524
|
} else if (arg === "--clean") {
|
|
@@ -526,7 +531,26 @@ async function main(): Promise<void> {
|
|
|
526
531
|
} else {
|
|
527
532
|
defines[define] = true;
|
|
528
533
|
}
|
|
529
|
-
} else if (
|
|
534
|
+
} else if (arg.startsWith("-I")) {
|
|
535
|
+
// Common mistake: -I is GCC syntax, we use --include
|
|
536
|
+
// Catches both "-I path" and "-Ipath" (no space)
|
|
537
|
+
console.error(`Error: Unknown flag '${arg}'`);
|
|
538
|
+
console.error(" Did you mean: --include <dir>");
|
|
539
|
+
console.error("");
|
|
540
|
+
console.error("Example:");
|
|
541
|
+
console.error(" cnext src --include path/to/headers");
|
|
542
|
+
process.exit(1);
|
|
543
|
+
} else if (arg.startsWith("--")) {
|
|
544
|
+
// Catch unknown long flags (e.g., --foo)
|
|
545
|
+
console.error(`Error: Unknown flag '${arg}'`);
|
|
546
|
+
showHelp();
|
|
547
|
+
process.exit(1);
|
|
548
|
+
} else if (arg.startsWith("-")) {
|
|
549
|
+
// Catch other unknown short flags (e.g., -x)
|
|
550
|
+
console.error(`Error: Unknown flag '${arg}'`);
|
|
551
|
+
showHelp();
|
|
552
|
+
process.exit(1);
|
|
553
|
+
} else {
|
|
530
554
|
inputFiles.push(arg);
|
|
531
555
|
}
|
|
532
556
|
}
|
|
@@ -563,6 +587,7 @@ async function main(): Promise<void> {
|
|
|
563
587
|
verbose,
|
|
564
588
|
cppRequired,
|
|
565
589
|
noCache,
|
|
590
|
+
parseOnly,
|
|
566
591
|
headerOutDir,
|
|
567
592
|
);
|
|
568
593
|
}
|
package/src/pipeline/Pipeline.ts
CHANGED
|
@@ -642,6 +642,9 @@ class Pipeline {
|
|
|
642
642
|
sourcePath: file.path, // Issue #230: For self-include header generation
|
|
643
643
|
generateHeaders: this.config.generateHeaders, // Issue #230: Enable self-include when headers are generated
|
|
644
644
|
cppMode: this.cppDetected, // Issue #250: C++ compatible code generation
|
|
645
|
+
sourceRelativePath: this.getSourceRelativePath(file.path), // Issue #339: For correct self-include paths
|
|
646
|
+
includeDirs: this.config.includeDirs, // Issue #349: For angle-bracket include resolution
|
|
647
|
+
inputs: this.config.inputs, // Issue #349: For calculating relative paths
|
|
645
648
|
},
|
|
646
649
|
);
|
|
647
650
|
|
|
@@ -699,14 +702,14 @@ class Pipeline {
|
|
|
699
702
|
}
|
|
700
703
|
|
|
701
704
|
/**
|
|
702
|
-
* Get
|
|
705
|
+
* Get relative path from any input directory for a file.
|
|
706
|
+
* Returns the relative path (e.g., "Display/Utils.cnx") or null if the file
|
|
707
|
+
* is not under any input directory.
|
|
708
|
+
*
|
|
709
|
+
* This is the shared logic used by getSourceRelativePath, getOutputPath,
|
|
710
|
+
* and getHeaderOutputPath for directory structure preservation.
|
|
703
711
|
*/
|
|
704
|
-
private
|
|
705
|
-
// Issue #211: Derive extension from cppDetected flag
|
|
706
|
-
const ext = this.cppDetected ? ".cpp" : ".c";
|
|
707
|
-
const outputName = basename(file.path).replace(/\.cnx$|\.cnext$/, ext);
|
|
708
|
-
|
|
709
|
-
// Check if file is in any input directory (for preserving structure)
|
|
712
|
+
private getRelativePathFromInputs(filePath: string): string | null {
|
|
710
713
|
for (const input of this.config.inputs) {
|
|
711
714
|
const resolvedInput = resolve(input);
|
|
712
715
|
|
|
@@ -715,24 +718,49 @@ class Pipeline {
|
|
|
715
718
|
continue;
|
|
716
719
|
}
|
|
717
720
|
|
|
718
|
-
const relativePath = relative(resolvedInput,
|
|
721
|
+
const relativePath = relative(resolvedInput, filePath);
|
|
719
722
|
|
|
720
723
|
// Check if file is under this input directory
|
|
721
724
|
if (relativePath && !relativePath.startsWith("..")) {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
+
return relativePath;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
725
728
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Issue #339: Get relative path from input directory for self-include generation.
|
|
734
|
+
* Returns the relative path (e.g., "Display/Utils.cnx") or just the basename
|
|
735
|
+
* if the file is not in any input directory.
|
|
736
|
+
*/
|
|
737
|
+
private getSourceRelativePath(filePath: string): string {
|
|
738
|
+
return this.getRelativePathFromInputs(filePath) ?? basename(filePath);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Get output path for a file
|
|
743
|
+
*/
|
|
744
|
+
private getOutputPath(file: IDiscoveredFile): string {
|
|
745
|
+
// Issue #211: Derive extension from cppDetected flag
|
|
746
|
+
const ext = this.cppDetected ? ".cpp" : ".c";
|
|
747
|
+
|
|
748
|
+
const relativePath = this.getRelativePathFromInputs(file.path);
|
|
749
|
+
if (relativePath) {
|
|
750
|
+
// File is under an input directory - preserve structure
|
|
751
|
+
const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ext);
|
|
752
|
+
const outputPath = join(this.config.outDir, outputRelative);
|
|
730
753
|
|
|
731
|
-
|
|
754
|
+
const outputDir = dirname(outputPath);
|
|
755
|
+
if (!existsSync(outputDir)) {
|
|
756
|
+
mkdirSync(outputDir, { recursive: true });
|
|
732
757
|
}
|
|
758
|
+
|
|
759
|
+
return outputPath;
|
|
733
760
|
}
|
|
734
761
|
|
|
735
762
|
// Fallback: flat output in outDir
|
|
763
|
+
const outputName = basename(file.path).replace(/\.cnx$|\.cnext$/, ext);
|
|
736
764
|
return join(this.config.outDir, outputName);
|
|
737
765
|
}
|
|
738
766
|
|
|
@@ -816,37 +844,25 @@ class Pipeline {
|
|
|
816
844
|
* Uses headerOutDir if specified, otherwise falls back to outDir
|
|
817
845
|
*/
|
|
818
846
|
private getHeaderOutputPath(file: IDiscoveredFile): string {
|
|
819
|
-
const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
|
|
820
|
-
|
|
821
847
|
// Use headerOutDir if specified, otherwise fall back to outDir
|
|
822
848
|
const headerDir = this.config.headerOutDir || this.config.outDir;
|
|
823
849
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
850
|
+
const relativePath = this.getRelativePathFromInputs(file.path);
|
|
851
|
+
if (relativePath) {
|
|
852
|
+
// File is under an input directory - preserve structure
|
|
853
|
+
const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".h");
|
|
854
|
+
const outputPath = join(headerDir, outputRelative);
|
|
827
855
|
|
|
828
|
-
|
|
829
|
-
if (existsSync(
|
|
830
|
-
|
|
856
|
+
const outputDir = dirname(outputPath);
|
|
857
|
+
if (!existsSync(outputDir)) {
|
|
858
|
+
mkdirSync(outputDir, { recursive: true });
|
|
831
859
|
}
|
|
832
860
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
// Check if file is under this input directory
|
|
836
|
-
if (relativePath && !relativePath.startsWith("..")) {
|
|
837
|
-
const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".h");
|
|
838
|
-
const outputPath = join(headerDir, outputRelative);
|
|
839
|
-
|
|
840
|
-
const outputDir = dirname(outputPath);
|
|
841
|
-
if (!existsSync(outputDir)) {
|
|
842
|
-
mkdirSync(outputDir, { recursive: true });
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
return outputPath;
|
|
846
|
-
}
|
|
861
|
+
return outputPath;
|
|
847
862
|
}
|
|
848
863
|
|
|
849
864
|
// Fallback: flat output in headerDir
|
|
865
|
+
const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
|
|
850
866
|
return join(headerDir, headerName);
|
|
851
867
|
}
|
|
852
868
|
|
package/src/project/Project.ts
CHANGED
|
@@ -37,6 +37,9 @@ interface IProjectConfig {
|
|
|
37
37
|
|
|
38
38
|
/** Issue #183: Disable symbol caching (default: false = cache enabled) */
|
|
39
39
|
noCache?: boolean;
|
|
40
|
+
|
|
41
|
+
/** Parse only mode - validate syntax without generating output */
|
|
42
|
+
parseOnly?: boolean;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export default IProjectConfig;
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
CPP14Parser,
|
|
10
|
+
ClassSpecifierContext,
|
|
11
|
+
} from "../parser/cpp/grammar/CPP14Parser";
|
|
9
12
|
import ISymbol from "../types/ISymbol";
|
|
10
13
|
import ESymbolKind from "../types/ESymbolKind";
|
|
11
14
|
import ESourceLanguage from "../types/ESourceLanguage";
|
|
@@ -187,13 +190,28 @@ class CppSymbolCollector {
|
|
|
187
190
|
|
|
188
191
|
const baseType = this.extractTypeFromDeclSpecSeq(declSpecSeq);
|
|
189
192
|
|
|
193
|
+
// Issue #342: Track anonymous class specifiers for typedef handling
|
|
194
|
+
let anonymousClassSpec: ClassSpecifierContext | null = null;
|
|
195
|
+
|
|
190
196
|
// Check for class specifier
|
|
191
197
|
for (const spec of declSpecSeq.declSpecifier?.() ?? []) {
|
|
192
198
|
const typeSpec = spec.typeSpecifier?.();
|
|
193
199
|
if (typeSpec) {
|
|
194
200
|
const classSpec = typeSpec.classSpecifier?.();
|
|
195
201
|
if (classSpec) {
|
|
196
|
-
this
|
|
202
|
+
// Check if this is a named struct/class
|
|
203
|
+
const classHead = classSpec.classHead?.();
|
|
204
|
+
const classHeadName = classHead?.classHeadName?.();
|
|
205
|
+
const className = classHeadName?.className?.();
|
|
206
|
+
const identifier = className?.Identifier?.();
|
|
207
|
+
|
|
208
|
+
if (identifier?.getText()) {
|
|
209
|
+
// Named struct - collect normally
|
|
210
|
+
this.collectClassSpecifier(classSpec, line);
|
|
211
|
+
} else {
|
|
212
|
+
// Issue #342: Anonymous struct - save for typedef handling below
|
|
213
|
+
anonymousClassSpec = classSpec;
|
|
214
|
+
}
|
|
197
215
|
}
|
|
198
216
|
|
|
199
217
|
const enumSpec = typeSpec.enumSpecifier?.();
|
|
@@ -218,6 +236,27 @@ class CppSymbolCollector {
|
|
|
218
236
|
? `${this.currentNamespace}::${name}`
|
|
219
237
|
: name;
|
|
220
238
|
|
|
239
|
+
// Issue #342: If we have an anonymous struct and this is a typedef,
|
|
240
|
+
// collect struct fields using the typedef name
|
|
241
|
+
if (anonymousClassSpec && this.symbolTable) {
|
|
242
|
+
const memberSpec = anonymousClassSpec.memberSpecification?.();
|
|
243
|
+
if (memberSpec) {
|
|
244
|
+
// Add the type symbol
|
|
245
|
+
this.symbols.push({
|
|
246
|
+
name: fullName,
|
|
247
|
+
kind: ESymbolKind.Class, // Treat typedef'd structs as classes
|
|
248
|
+
sourceFile: this.sourceFile,
|
|
249
|
+
sourceLine: line,
|
|
250
|
+
sourceLanguage: ESourceLanguage.Cpp,
|
|
251
|
+
isExported: true,
|
|
252
|
+
parent: this.currentNamespace,
|
|
253
|
+
});
|
|
254
|
+
// Collect members using the typedef name
|
|
255
|
+
this.collectClassMembers(fullName, memberSpec);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
221
260
|
// Issue #322: Extract parameters for function declarations
|
|
222
261
|
const params = isFunction
|
|
223
262
|
? this.extractFunctionParameters(declarator)
|