c-next 0.1.24 → 0.1.25

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -1430,13 +1430,17 @@ export default class CodeGenerator implements IOrchestrator {
1430
1430
  // Issue #230: Self-include for extern "C" linkage
1431
1431
  // When file has public symbols and headers are being generated,
1432
1432
  // include own header to ensure proper C linkage
1433
+ // Issue #339: Use relative path from source root when available
1433
1434
  if (
1434
1435
  options?.generateHeaders &&
1435
1436
  this.symbols!.hasPublicSymbols() &&
1436
1437
  this.sourcePath
1437
1438
  ) {
1438
- const basename = this.sourcePath.replace(/^.*[\\/]/, "");
1439
- const headerName = basename.replace(/\.cnx$|\.cnext$/, ".h");
1439
+ // Issue #339: Prefer sourceRelativePath for correct directory structure
1440
+ // Otherwise fall back to basename for backward compatibility
1441
+ const pathToUse =
1442
+ options.sourceRelativePath || this.sourcePath.replace(/^.*[\\/]/, "");
1443
+ const headerName = pathToUse.replace(/\.cnx$|\.cnext$/, ".h");
1440
1444
  output.push(`#include "${headerName}"`);
1441
1445
  output.push("");
1442
1446
  }
@@ -18,6 +18,12 @@ 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;
21
27
  }
22
28
 
23
29
  export default ICodeGeneratorOptions;
@@ -642,6 +642,7 @@ 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
645
646
  },
646
647
  );
647
648
 
@@ -699,14 +700,14 @@ class Pipeline {
699
700
  }
700
701
 
701
702
  /**
702
- * Get output path for a file
703
+ * Get relative path from any input directory for a file.
704
+ * Returns the relative path (e.g., "Display/Utils.cnx") or null if the file
705
+ * is not under any input directory.
706
+ *
707
+ * This is the shared logic used by getSourceRelativePath, getOutputPath,
708
+ * and getHeaderOutputPath for directory structure preservation.
703
709
  */
704
- private getOutputPath(file: IDiscoveredFile): string {
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)
710
+ private getRelativePathFromInputs(filePath: string): string | null {
710
711
  for (const input of this.config.inputs) {
711
712
  const resolvedInput = resolve(input);
712
713
 
@@ -715,24 +716,49 @@ class Pipeline {
715
716
  continue;
716
717
  }
717
718
 
718
- const relativePath = relative(resolvedInput, file.path);
719
+ const relativePath = relative(resolvedInput, filePath);
719
720
 
720
721
  // Check if file is under this input directory
721
722
  if (relativePath && !relativePath.startsWith("..")) {
722
- // File is under this input directory - preserve structure
723
- const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ext);
724
- const outputPath = join(this.config.outDir, outputRelative);
723
+ return relativePath;
724
+ }
725
+ }
725
726
 
726
- const outputDir = dirname(outputPath);
727
- if (!existsSync(outputDir)) {
728
- mkdirSync(outputDir, { recursive: true });
729
- }
727
+ return null;
728
+ }
729
+
730
+ /**
731
+ * Issue #339: Get relative path from input directory for self-include generation.
732
+ * Returns the relative path (e.g., "Display/Utils.cnx") or just the basename
733
+ * if the file is not in any input directory.
734
+ */
735
+ private getSourceRelativePath(filePath: string): string {
736
+ return this.getRelativePathFromInputs(filePath) ?? basename(filePath);
737
+ }
738
+
739
+ /**
740
+ * Get output path for a file
741
+ */
742
+ private getOutputPath(file: IDiscoveredFile): string {
743
+ // Issue #211: Derive extension from cppDetected flag
744
+ const ext = this.cppDetected ? ".cpp" : ".c";
745
+
746
+ const relativePath = this.getRelativePathFromInputs(file.path);
747
+ if (relativePath) {
748
+ // File is under an input directory - preserve structure
749
+ const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ext);
750
+ const outputPath = join(this.config.outDir, outputRelative);
730
751
 
731
- return outputPath;
752
+ const outputDir = dirname(outputPath);
753
+ if (!existsSync(outputDir)) {
754
+ mkdirSync(outputDir, { recursive: true });
732
755
  }
756
+
757
+ return outputPath;
733
758
  }
734
759
 
735
760
  // Fallback: flat output in outDir
761
+ const outputName = basename(file.path).replace(/\.cnx$|\.cnext$/, ext);
736
762
  return join(this.config.outDir, outputName);
737
763
  }
738
764
 
@@ -816,37 +842,25 @@ class Pipeline {
816
842
  * Uses headerOutDir if specified, otherwise falls back to outDir
817
843
  */
818
844
  private getHeaderOutputPath(file: IDiscoveredFile): string {
819
- const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
820
-
821
845
  // Use headerOutDir if specified, otherwise fall back to outDir
822
846
  const headerDir = this.config.headerOutDir || this.config.outDir;
823
847
 
824
- // Check if file is in any input directory (for preserving structure)
825
- for (const input of this.config.inputs) {
826
- const resolvedInput = resolve(input);
848
+ const relativePath = this.getRelativePathFromInputs(file.path);
849
+ if (relativePath) {
850
+ // File is under an input directory - preserve structure
851
+ const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".h");
852
+ const outputPath = join(headerDir, outputRelative);
827
853
 
828
- // Skip if input is a file (not a directory) - can't preserve structure
829
- if (existsSync(resolvedInput) && statSync(resolvedInput).isFile()) {
830
- continue;
854
+ const outputDir = dirname(outputPath);
855
+ if (!existsSync(outputDir)) {
856
+ mkdirSync(outputDir, { recursive: true });
831
857
  }
832
858
 
833
- const relativePath = relative(resolvedInput, file.path);
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
- }
859
+ return outputPath;
847
860
  }
848
861
 
849
862
  // Fallback: flat output in headerDir
863
+ const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
850
864
  return join(headerDir, headerName);
851
865
  }
852
866
 
@@ -5,7 +5,10 @@
5
5
 
6
6
  /* eslint-disable @typescript-eslint/no-explicit-any */
7
7
 
8
- import { CPP14Parser } from "../parser/cpp/grammar/CPP14Parser";
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.collectClassSpecifier(classSpec, line);
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)