yini-parser 1.4.3 → 1.5.0

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +18 -12
  3. package/dist/YINI.d.ts +9 -13
  4. package/dist/YINI.js +3 -3
  5. package/dist/YINI.js.map +1 -1
  6. package/dist/core/astBuilder.d.ts +3 -6
  7. package/dist/core/astBuilder.js +80 -49
  8. package/dist/core/astBuilder.js.map +1 -1
  9. package/dist/core/objectBuilder.d.ts +8 -4
  10. package/dist/core/objectBuilder.js +31 -61
  11. package/dist/core/objectBuilder.js.map +1 -1
  12. package/dist/core/options/defaultParserOptions.js +9 -1
  13. package/dist/core/options/defaultParserOptions.js.map +1 -1
  14. package/dist/core/options/optionsFunctions.js +1 -3
  15. package/dist/core/options/optionsFunctions.js.map +1 -1
  16. package/dist/core/parsingRules/modeFromRulesMatcher.d.ts +1 -1
  17. package/dist/core/parsingRules/modeFromRulesMatcher.js +22 -13
  18. package/dist/core/parsingRules/modeFromRulesMatcher.js.map +1 -1
  19. package/dist/core/pipeline/pipeline.js +4 -0
  20. package/dist/core/pipeline/pipeline.js.map +1 -1
  21. package/dist/dev/main.js +55 -84
  22. package/dist/dev/main.js.map +1 -1
  23. package/dist/grammar/generated/YiniLexer.d.ts +43 -49
  24. package/dist/grammar/generated/YiniLexer.js +320 -332
  25. package/dist/grammar/generated/YiniLexer.js.map +1 -1
  26. package/dist/grammar/generated/YiniParser.d.ts +60 -82
  27. package/dist/grammar/generated/YiniParser.js +461 -726
  28. package/dist/grammar/generated/YiniParser.js.map +1 -1
  29. package/dist/grammar/generated/YiniParserVisitor.d.ts +7 -7
  30. package/dist/grammar/generated/YiniParserVisitor.js +1 -1
  31. package/dist/types/index.d.ts +1 -1
  32. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.5.0 - 2026 Apr
4
+ - **Updated:** Parser behavior aligned with YINI Specification `v1.0.0-RC.5`.
5
+ - **Changed:** In strict mode, YINI documents must now end with the document terminator `/END`.
6
+ - **Changed:** Strict mode now requires exactly one explicit top-level section.
7
+ - **Updated:** Section header parsing now reflects the latest horizontal whitespace (`HSPACE`) rules from the specification.
8
+ - **Improved:** Refined how members outside explicit sections are handled in lenient mode, and added stricter checks for top-level structure in strict mode.
9
+ - **Improved:** Clarified and tightened how empty values and explicit `null` are handled in lenient and strict mode.
10
+ - **Improved:** Fixed `throwOnError` option detection, clarified the related documentation, and cleaned up public parameter names.
11
+ - **Fixed:** Made `throwOnError` work consistently together with the selected fail level.
12
+ - **Updated:** Synced to the latest lexer and parser grammar files from the Specification Package `v1.0.0-RC.5`, with cleaned up and refined grammar files for better consistency and maintainability.
13
+ - **Added:** Expanded validation and tests for section headers, including shorthand markers, backticked names, and invalid dotted section names.
14
+ - **Expanded:** Improved integration and smoke test coverage for error recovery, throw behavior, null handling, fixture parsing, and section/header edge cases.
15
+ - **Updated:** Added and validated a new large smoke/golden fixture:
16
+ - `tests/fixtures/smoke-fixtures/c-industrial-monitoring-and-automation-platform.smoke.yini`
17
+ - **Improved:** Fixed and re-enabled previously skipped smoke tests:
18
+ - `tests/fixtures/smoke-fixtures/8-api-keys-integration.smoke.yini`
19
+ - `tests/fixtures/smoke-fixtures/9-app-preferences.smoke.yini`
20
+
3
21
  ## 1.4.3 - 2026 Apr
22
+ - **Promoted:** YINI Parser TypeScript is now considered stable (non-beta) after iterative beta releases and refinements.
4
23
  - **Fixed:** Rebuilt the project and reduced reported vulnerabilities from 4 to 0.
5
24
 
6
25
  ## 1.4.3-beta - 2026 Mar
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # yini-parser
2
- > **Readable configuration for Node.js and TypeScript — without YAML foot-guns or JSON noise.**
2
+ > **Readable configuration for Node.js and TypeScript/JavaScript — without YAML footguns or JSON noise.**
3
3
 
4
- The official TypeScript / Node.js parser for **YINI** (by the YINI-lang project) — a human-friendly configuration format with real structure, nested sections, comments, and predictable parsing.
4
+ The official TypeScript / Node.js parser for **YINI** (by the YINI-lang project) — a human-friendly configuration format with clear structure, nested sections, comments, and predictable parsing.
5
5
 
6
6
  YINI is designed for applications, tools, and services that need configuration that stays readable for humans without becoming vague, fragile, or hard to maintain.
7
7
 
8
- [![npm version](https://img.shields.io/npm/v/yini-parser.svg)](https://www.npmjs.com/package/yini-parser) [![All Test Suites](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml) [![All Regression Tests](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml) [![Grammar Drift Check](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml) [![npm downloads](https://img.shields.io/npm/dm/yini-parser)](https://www.npmjs.com/package/yini-parser)
8
+ [![npm version](https://img.shields.io/npm/v/yini-parser.svg)](https://www.npmjs.com/package/yini-parser) [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![All Test Suites](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml) [![All Regression Tests](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml) [![Grammar Drift Check](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml) [![npm downloads](https://img.shields.io/npm/dm/yini-parser)](https://www.npmjs.com/package/yini-parser)
9
9
 
10
10
  ## Quick Start
11
11
 
@@ -36,18 +36,18 @@ console.log(config.App.Features.caching) // true
36
36
  ## 🙋‍♀️ Why try YINI?
37
37
 
38
38
  - **Readable by humans** — Less noisy than JSON, less fragile than indentation-driven formats.
39
- - **Structured enough for real configuration** — Sections, nested sections, lists, objects, booleans, and null.
39
+ - **Structured for real-world configuration** — Sections, nested sections, lists, objects, booleans, and null.
40
40
  - **Predictable parsing** — Explicit syntax with clear rules.
41
41
  - **Easy to use from TypeScript/Node.js** — Parse from strings or files in a few lines.
42
42
 
43
43
  ---
44
44
 
45
45
  ## What YINI looks like in practice
46
- > A basic YINI configuration example, showing a section, nested section, comments:
46
+ > A basic YINI configuration example, showing a section, a nested section, and comments:
47
47
  ![YINI Config Example](./samples/basic.yini.png)
48
48
  Source: [basic.yini](./samples/basic.yini)
49
49
 
50
- - ▶️ Link to [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete basic usage.
50
+ - ▶️ [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete usage examples.
51
51
 
52
52
  ---
53
53
 
@@ -55,7 +55,7 @@ Source: [basic.yini](./samples/basic.yini)
55
55
  - **Indentation-independent structure:** Spaces and tabs never change meaning, so files can be reformatted without changing structure.
56
56
  - **Explicit nesting:** Hierarchy is defined with section markers like `^`, `^^`, and `^^^`, making large configurations easier to scan and refactor.
57
57
  - **Multiple data types:** Supports booleans (`true` / `false`, `yes` / `no`, etc.), numbers, lists, and inline objects, with explicit string syntax.
58
- - **Comment support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`), making it easier to document configuration directly in the file.
58
+ - **Comment support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`), making it easy to document configuration directly in the file.
59
59
  - **Predictable parsing:** Clear rules with optional strict and lenient modes for different use cases.
60
60
 
61
61
  ---
@@ -141,7 +141,7 @@ Source: [config.yini](./samples/config.yini)
141
141
 
142
142
  ## 🧪 Testing and Stability
143
143
 
144
- This parser is continuously validated through comprehensive regression and smoke tests, ensuring deterministic parsing behavior across default, strict, and metadata-enabled modes.
144
+ This parser is validated through regression and smoke tests to help ensure stable, predictable parsing across default, strict, and metadata-enabled modes.
145
145
 
146
146
  ---
147
147
 
@@ -149,16 +149,22 @@ This parser is continuously validated through comprehensive regression and smoke
149
149
  - ➡️ [YINI Homepage](https://yini-lang.org)
150
150
  *Tutorials, guides, and examples.*
151
151
 
152
+ - ➡️ [Read the YINI Specification](https://yini-lang.org/refs/specification)
153
+ *Full syntax and format reference.*
154
+
152
155
  - ➡️ [YINI CLI on GitHub](https://github.com/YINI-lang/yini-cli)
153
156
  *CLI tooling for working with YINI files.*
154
157
 
155
- - ➡️ [YINI Project](https://github.com/YINI-lang)
158
+ - ➡️ [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main)
159
+ *Complete basic usage examples.*
160
+
161
+ - ➡️ [YINI-lang Project](https://github.com/YINI-lang)
156
162
  *Repositories and related ecosystem projects.*
157
163
 
158
164
  ---
159
165
 
160
166
  ## 🤝 Contributing
161
- We welcome feedback, bug reports, feature requests, and code contributions!
167
+ Feedback, bug reports, feature requests, and code contributions are welcome.
162
168
  - [Open an Issue](https://github.com/YINI-lang/yini-parser-typescript/issues)
163
169
  - [Start a Discussion](https://github.com/YINI-lang/yini-parser-typescript/discussions)
164
170
 
@@ -171,9 +177,9 @@ If this library is useful to you, a GitHub star helps more people discover the p
171
177
  ---
172
178
 
173
179
  ## License
174
- This project is licensed under the Apache-2.0 license — see the [LICENSE](./LICENSE) file for details.
180
+ This project is licensed under the Apache License 2.0 — see the [LICENSE](./LICENSE) file for details.
175
181
 
176
- In this project on GitHub, the `libs` directory contains third party software and each is licensed under its own license which is described in its own license file under the respective directory under `libs`.
182
+ In this project on GitHub, the `libs` directory contains third-party software and each is licensed under its own license, described in its own license file under the respective directory under `libs`.
177
183
 
178
184
  ---
179
185
 
package/dist/YINI.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { ParsedObject, ParseOptions, PreferredFailLevel, YiniParseResult } from './types';
2
2
  /**
3
- * This class is the public API, which exposes only parse(..) and
4
- * parseFile(..), rest of the implementation details are hidden.
5
- * @note Only parse and parseFile are public.
3
+ * This class is the main public API. It exposes `parse(..)` and `parseFile(..)`
4
+ * as the primary entry points, while the implementation details remain internal.
5
+ * @note The public parsing API is exposed through `parse(..)` and `parseFile(..)`.
6
6
  */
7
7
  export default class YINI {
8
8
  private static g_tabSize;
@@ -73,16 +73,14 @@ export default class YINI {
73
73
  * Does not affect warnings included in returned metadata.
74
74
  * @param options.requireDocTerminator - Controls whether a document terminator is required.
75
75
  * Allowed values: `'optional'` | `'warn-if-missing'` | `'required'`.
76
- * @param options.silent - Suppress all output (even errors, exit code only).
76
+ * @param options.silent - Suppress all console output, including errors and warnings.
77
77
  * @param options.strictMode - Sets the baseline ruleset (true = strict, false = lenient).
78
78
  * This is only a starting point: rule-specific options (e.g., `treatEmptyValueAsNull`,
79
79
  * `onDuplicateKey`, etc.) can override parts of that ruleset. If any overrides are given,
80
80
  * the effective mode becomes **custom** rather than purely strict/lenient.
81
81
  * @param options.treatEmptyValueAsNull - How to treat an explicitly empty value on the
82
82
  * right-hand side of '='. Allowed values: `'allow'` | `'allow-with-warning'` | `'disallow'`.
83
- * @param options.throwOnError - Will throw on first parse error encountered.
84
- * NOTE: Current default is `true`. The default will change to `false` in the next
85
- * release. To avoid breaking changes, set this option explicitly.
83
+ * @param options.throwOnError - Throw when a parse issue reaches the active bail threshold (for example, on errors if `failLevel = 'errors'`).
86
84
  *
87
85
  * @returns {ParsedObject | YiniParseResult} The parsed YINI content.
88
86
  *
@@ -106,7 +104,7 @@ export default class YINI {
106
104
  /**
107
105
  * Parse a YINI file into a JavaScript object.
108
106
  *
109
- * @param yiniFile Path to the YINI file.
107
+ * @param filePath Path to the YINI file.
110
108
  * @param strictMode If `true`, enforce strict parsing rules (e.g. require `/END`, disallow trailing commas).
111
109
  * @param failLevel Preferred bail sensitivity level, controls if and when parsing should stop on problems:
112
110
  * - `'auto'` (default) : Auto‑select level (strict → `'errors'`, lenient → `'ignore-errors'`)
@@ -137,7 +135,7 @@ export default class YINI {
137
135
  /**
138
136
  * Parse a YINI file into a JavaScript object, using an options object for configuration.
139
137
  *
140
- * @param yiniFile Path to the YINI file.
138
+ * @param filePath Path to the YINI file.
141
139
  * @param options Optional settings to customize parsing and/or results, useful if you need more control.
142
140
  * For all options, see types/ParseOptions.
143
141
  *
@@ -161,16 +159,14 @@ export default class YINI {
161
159
  * Does not affect warnings included in returned metadata.
162
160
  * @param options.requireDocTerminator - Controls whether a document terminator is required.
163
161
  * Allowed values: `'optional'` | `'warn-if-missing'` | `'required'`.
164
- * @param options.silent - Suppress all output (even errors, exit code only).
162
+ * @param options.silent - Suppress all console output, including errors and warnings.
165
163
  * @param options.strictMode - Sets the baseline ruleset (true = strict, false = lenient).
166
164
  * This is only a starting point: rule-specific options (e.g., `treatEmptyValueAsNull`,
167
165
  * `onDuplicateKey`, etc.) can override parts of that ruleset. If any overrides are given,
168
166
  * the effective mode becomes **custom** rather than purely strict/lenient.
169
167
  * @param options.treatEmptyValueAsNull - How to treat an explicitly empty value on the
170
168
  * right-hand side of '='. Allowed values: `'allow'` | `'allow-with-warning'` | `'disallow'`.
171
- * @param options.throwOnError - Will throw on first parse error encountered.
172
- * NOTE: Current default is `true`. The default will change to `false` in the next
173
- * release. To avoid breaking changes, set this option explicitly.
169
+ * @param options.throwOnError - Throw when a parse issue reaches the active bail threshold (for example, on errors if `failLevel = 'errors'`).
174
170
  *
175
171
  * @returns {ParsedObject | YiniParseResult} The parsed YINI content.
176
172
  *
package/dist/YINI.js CHANGED
@@ -7,9 +7,9 @@ const runtime_1 = require("./core/runtime");
7
7
  const print_1 = require("./utils/print");
8
8
  const DEFAULT_TAB_SIZE = 4; // De facto "modern default" (even though traditionally/historically it's 8).
9
9
  /**
10
- * This class is the public API, which exposes only parse(..) and
11
- * parseFile(..), rest of the implementation details are hidden.
12
- * @note Only parse and parseFile are public.
10
+ * This class is the main public API. It exposes `parse(..)` and `parseFile(..)`
11
+ * as the primary entry points, while the implementation details remain internal.
12
+ * @note The public parsing API is exposed through `parse(..)` and `parseFile(..)`.
13
13
  */
14
14
  class YINI {
15
15
  /**
package/dist/YINI.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"YINI.js","sourceRoot":"","sources":["../src/YINI.ts"],"names":[],"mappings":";;AAAA,sCAA6C;AAC7C,8DAA0D;AAC1D,sEAAqE;AACrE,4CAA4C;AAO5C,yCAAiE;AAEjE,MAAM,gBAAgB,GAAG,CAAC,CAAA,CAAC,6EAA6E;AAExG;;;;GAIG;AACH,MAAqB,IAAI;IAIrB;;OAEG;IACI,MAAM,CAAC,UAAU;QACpB,OAAO,IAAI,CAAC,SAAS,CAAA;IACzB,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,UAAU,CAAC,MAAc;QACnC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,EAAE,EAAE,CAAC;YAC5B,IAAI,mCAAgB,CAAC,aAAa,CAAC,CAAC,UAAU,CAC1C,SAAS,EACT,aAAa,EACb,oBAAoB,MAAM,mBAAmB,EAC7C,2CAA2C,CAC9C,CAAA;YACD,MAAM,IAAI,UAAU,CAAC,YAAY,MAAM,0BAA0B,CAAC,CAAA;QACtE,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,MAAM,CAAA;IAC3B,CAAC;IA6GD,yEAAyE;IACzE,gGAAgG;IAChG,6HAA6H;IACtH,MAAM,CAAC,KAAK,CACf,WAAmB,EACnB,IAA6B,EAAE,uBAAuB;IACtD,YAAgC,MAAM,EACtC,eAAe,GAAG,KAAK;QAEvB,IAAA,kBAAU,EAAC,6CAA6C,CAAC,CAAA;QAEzD,IAAA,kBAAU,GAAE,CAAA;QACZ,IAAA,kBAAU,EACN,8DAA8D,CACjE,CAAA;QACD,MAAM,OAAO,GAAG,IAAI,qBAAW,CAAC,QAAQ,CAAC,CAAA;QAEzC,MAAM,MAAM,GAAG,IAAA,sCAAmB,EAAC,IAAI,CAAC;YACpC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,kCAAkC;YACxE,CAAC,CAAC,OAAO,CAAC,QAAQ;YACZ,4DAA4D;YAC5D,WAAW,EACX,IAA2B,EAC3B,SAAS,EACT,eAAe,CAClB,CAAA;QACP,IAAA,kBAAU,EAAC,kDAAkD,CAAC,CAAA;QAE9D,IAAI,IAAA,WAAK,GAAE,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,EAAE,CAAA;YACb,IAAA,gBAAQ,EAAC,yBAAyB,CAAC,CAAA;YACnC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAEnB,IAAA,gBAAQ,EAAC,kBAAkB,CAAC,CAAA;YAC5B,IAAA,mBAAW,EAAC,MAAM,CAAC,CAAA;QACvB,CAAC;QAED,OAAO,MAAM,CAAA;IACjB,CAAC;IA6GD,yEAAyE;IACzE,gGAAgG;IAChG,6HAA6H;IACtH,MAAM,CAAC,SAAS,CACnB,QAAgB,EAChB,IAA6B,EAAE,uBAAuB;IACtD,YAAgC,MAAM,EACtC,eAAe,GAAG,KAAK;QAEvB,IAAA,kBAAU,EAAC,iDAAiD,CAAC,CAAA;QAC7D,IAAA,kBAAU,EAAC,sBAAsB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QAElD,IAAA,kBAAU,GAAE,CAAA;QACZ,IAAA,kBAAU,EACN,iEAAiE,CACpE,CAAA;QACD,MAAM,OAAO,GAAG,IAAI,qBAAW,CAAC,MAAM,CAAC,CAAA;QAEvC,MAAM,MAAM,GAAG,IAAA,sCAAmB,EAAC,IAAI,CAAC;YACpC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,kCAAkC;YACxE,CAAC,CAAC,OAAO,CAAC,WAAW;YACf,4DAA4D;YAC5D,QAAQ,EACR,IAA2B,EAC3B,SAAS,EACT,eAAe,CAClB,CAAA;QAEP,IAAA,kBAAU,EAAC,kDAAkD,CAAC,CAAA;QAC9D,OAAO,MAAM,CAAA;IACjB,CAAC;;AAvTD,2IAA2I;AAC5H,cAAS,GAAG,gBAAgB,CAAA,CAAC,0CAA0C;kBAFrE,IAAI"}
1
+ {"version":3,"file":"YINI.js","sourceRoot":"","sources":["../src/YINI.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { AnnotationContext, AssignmentContext, Bad_memberContext, Bad_meta_textContext, Boolean_literalContext, Colon_list_declContext, DirectiveContext, ElementsContext, EolContext, List_literalContext, MemberContext, Meta_stmtContext, Null_literalContext, Number_literalContext, Object_literalContext, Object_memberContext, Object_membersContext, PrologContext, StmtContext, String_concatContext, String_literalContext, Terminal_stmtContext, ValueContext, YiniContext } from '../grammar/generated/YiniParser.js';
1
+ import { AnnotationContext, AssignmentContext, Bad_memberContext, Bad_meta_textContext, Boolean_literalContext, DirectiveContext, ElementsContext, EolContext, List_literalContext, MemberContext, Meta_stmtContext, Null_literalContext, Number_literalContext, Object_literalContext, Object_memberContext, Object_membersContext, PrologContext, StmtContext, String_concatContext, String_literalContext, Terminal_stmtContext, ValueContext, YiniContext } from '../grammar/generated/YiniParser.js';
2
2
  import YiniParserVisitor from '../grammar/generated/YiniParserVisitor';
3
3
  import { ErrorDataHandler } from './errorDataHandler';
4
4
  import { IParseCoreOptions, IYiniAST, TSourceType } from './internalTypes';
@@ -28,6 +28,7 @@ export default class ASTBuilder<Result> extends YiniParserVisitor<Result> {
28
28
  * @param metaLineCount Provide the line-count here so the meta information can be updated accordingly.
29
29
  */
30
30
  constructor(errorHandler: ErrorDataHandler, options: IParseCoreOptions, sourceType: TSourceType, metaFileName: string | null);
31
+ private validateStrictTopLevelStructure;
31
32
  private hasDefinedSectionTitle;
32
33
  private setDefineSectionTitle;
33
34
  private extractStringParts;
@@ -166,12 +167,8 @@ export default class ASTBuilder<Result> extends YiniParserVisitor<Result> {
166
167
  */
167
168
  visitObject_member: (ctx: Object_memberContext) => any;
168
169
  /**
169
- * Visit a parse tree produced by `YiniParser.colon_list_decl`.
170
- * @param ctx the parse tree
171
- * @grammarRule KEY WS? COLON (eol | WS+)* elements (eol | WS+)* eol
172
- * @return the visitor result
170
+ * @note Colon list not supported any more since YINI Spec Package v1.0.0.rc4
173
171
  */
174
- visitColon_list_decl: (ctx: Colon_list_declContext) => any;
175
172
  /**
176
173
  * Visit a parse tree produced by `YiniParser.string_concat`.
177
174
  * @param ctx the parse tree
@@ -40,6 +40,7 @@ const env_1 = require("../config/env");
40
40
  const YiniParserVisitor_1 = __importDefault(require("../grammar/generated/YiniParserVisitor"));
41
41
  const extractSignificantYiniLine_1 = require("../parsers/extractSignificantYiniLine");
42
42
  const parseBoolean_1 = __importDefault(require("../parsers/parseBoolean"));
43
+ const parseNull_1 = __importDefault(require("../parsers/parseNull"));
43
44
  const parseNumber_1 = __importDefault(require("../parsers/parseNumber"));
44
45
  // import parseNumber from '../parsers/parseNumber'
45
46
  const parseSectionHeader_1 = __importDefault(require("../parsers/parseSectionHeader"));
@@ -71,6 +72,7 @@ const makeScalarValue = (type, value = null, tag = undefined) => {
71
72
  case 'Undefined':
72
73
  return { type: 'Undefined', value: undefined, tag };
73
74
  default:
75
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
74
76
  new errorDataHandler_1.ErrorDataHandler(_sourceType).pushOrBail(undefined, 'Fatal-Error', `No such type in makeValue(..), type: ${type}, value: ${value}`, 'Something in the code is done incorrectly in order for this to happen... :S');
75
77
  }
76
78
  return { type: 'Null', value: null, tag };
@@ -162,7 +164,6 @@ class ASTBuilder extends YiniParserVisitor_1.default {
162
164
  // private meta_numOfChains = 0 // For stats.
163
165
  this.meta_maxLevel = 0; // For stats.
164
166
  this.mapSectionNamePaths = new Map();
165
- // --- Private helper methods --------------------------------
166
167
  this.hasDefinedSectionTitle = (keyPath) => {
167
168
  return this.mapSectionNamePaths?.has(keyPath);
168
169
  };
@@ -229,17 +230,18 @@ class ASTBuilder extends YiniParserVisitor_1.default {
229
230
  this.visitStmt = (ctx) => {
230
231
  const child = ctx.getChild(0);
231
232
  const ruleName = child?.constructor?.name ?? '';
233
+ // debugPrint('S0')
234
+ // const badHeaderWDotName = ctx.BAD_SECTION_HEAD_W_DOT_NAME()?.getText()
235
+ // if (badHeaderWDotName) {
236
+ // console.log('QQQQQQQQ = ' + badHeaderWDotName)
237
+ // }
232
238
  if (ruleName.includes('EolContext'))
233
239
  return this.visitEol?.(child);
234
240
  if (ruleName.includes('AssignmentContext'))
235
241
  return this.visitAssignment?.(child);
236
- if (ruleName.includes('Colon_list_declContext'))
237
- return this.visitColon_list_decl?.(child);
238
242
  if (ruleName.includes('Meta_stmtContext'))
239
243
  return this.visitMeta_stmt?.(child);
240
244
  (0, print_1.debugPrint)('S1');
241
- // let headerAlt = child.getText?.() ?? ''
242
- // let header = ctx.SECTION_HEAD()?.getText().trim() || ''
243
245
  let header = ctx.SECTION_HEAD()?.getText().trim() || '';
244
246
  // debugPrint('S2, lineAlt: >>>' + lineAlt + '<<<')
245
247
  (0, print_1.debugPrint)('S2, header: >>>' + header + '<<<');
@@ -423,9 +425,11 @@ class ASTBuilder extends YiniParserVisitor_1.default {
423
425
  break;
424
426
  case 'allow-with-warning':
425
427
  valueNode = makeScalarValue('Null', null, 'Implicit null (empty value)');
428
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
426
429
  this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Warning', `Empty value treated as null for key '${resultKey}'.`, `An empty value after '=' was encountered. Per 'treatEmptyValueAsNull = allow-with-warning', interpreted as null.`, `If you intended null, write it explicitly: ${resultKey} = null. Otherwise provide a non-empty value or set 'treatEmptyValueAsNull' to 'disallow'.`);
427
430
  break;
428
431
  case 'disallow':
432
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
429
433
  this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', `Missing value for key '${resultKey}'`, `Expected a value after '=' but found none. Implicit nulls are disallowed by 'treatEmptyValueAsNull = disallow'.`, `Write 'null' explicitly (${resultKey} = null) if that is intended, or provide a concrete value.`);
430
434
  return makeScalarValue('Undefined', undefined, 'Missing value already reported');
431
435
  }
@@ -459,12 +463,14 @@ class ASTBuilder extends YiniParserVisitor_1.default {
459
463
  )
460
464
  }*/
461
465
  if (!valueNode) {
466
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
462
467
  this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Invalid value', `Invalid value for key '${resultKey}' in member (<key> = <value> pair).`, `Got '${rawValue}', but expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`);
463
468
  }
464
469
  else if (valueNode.type === 'Undefined' &&
465
470
  valueNode.tag !== 'Invalid string literal already reported' &&
466
471
  valueNode.tag !== 'Missing value already reported' &&
467
472
  valueNode.tag !== 'Parser syntax error already reported') {
473
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
468
474
  this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Invalid value', `Invalid value for key '${resultKey}' in member (<key> = <value> pair).`, `Got '${rawValue}', but expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`);
469
475
  }
470
476
  // It keeps the sentinel tags useful for control flow inside visitMember(..),
@@ -622,6 +628,7 @@ class ASTBuilder extends YiniParserVisitor_1.default {
622
628
  'Check that all escape sequences in the C-string are complete and valid.';
623
629
  }
624
630
  }
631
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
625
632
  this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', msgWhat, msgWhy, msgHint);
626
633
  return makeScalarValue('Undefined', undefined, 'Invalid string literal already reported');
627
634
  }
@@ -687,8 +694,14 @@ class ASTBuilder extends YiniParserVisitor_1.default {
687
694
  // visitNull_literal?: (ctx: Null_literalContext) => Result
688
695
  this.visitNull_literal = (ctx) => {
689
696
  (0, print_1.debugPrint)('-> Entered visitNull_literal(..)');
690
- (0, print_1.debugPrint)('raw = ' + ctx.getText());
691
- return makeScalarValue('Null', null, 'Explicit Null');
697
+ // debugPrint('raw = ' + ctx.getText())
698
+ // return makeScalarValue('Null', null, 'Explicit Null')
699
+ const raw = ctx.getText();
700
+ (0, print_1.debugPrint)('raw: "' + raw + '"');
701
+ const parsed = (0, parseNull_1.default)(raw);
702
+ (0, print_1.debugPrint)('parsed: "' + parsed + '"');
703
+ // Case-insensitive true/false/on/off/yes/no (Spec section, 8.1).
704
+ return makeScalarValue('Null', parsed, 'Explicit Null');
692
705
  };
693
706
  /**
694
707
  * Visit a parse tree produced by `YiniParser.list_literal`.
@@ -767,7 +780,8 @@ class ASTBuilder extends YiniParserVisitor_1.default {
767
780
  this.visitObject_members = (ctx) => {
768
781
  (0, print_1.debugPrint)('-> Entered visitObject_members(..)');
769
782
  (0, print_1.debugPrint)('entries.length = ' + ctx?.object_member_list().length);
770
- const entries = [];
783
+ // const entries: Array<{ k: string; v: TValueLiteral }> = []
784
+ const entries = {};
771
785
  ctx.object_member_list().forEach((member) => {
772
786
  const { key, value } = this.visitObject_member(member);
773
787
  (0, print_1.debugPrint)(' key = ' + key);
@@ -790,7 +804,7 @@ class ASTBuilder extends YiniParserVisitor_1.default {
790
804
  const rawValue = ctx.value().getText();
791
805
  const valueNode = ctx.value()
792
806
  ? this.visitValue(ctx.value())
793
- : makeScalarValue('Null', 'Implicit Null');
807
+ : makeScalarValue('Null', null, 'Implicit Null');
794
808
  (0, print_1.debugPrint)(' rawKey = ' + rawKey);
795
809
  (0, print_1.debugPrint)(' key = ' + key);
796
810
  (0, print_1.debugPrint)('rawValue = ' + rawValue);
@@ -806,30 +820,32 @@ class ASTBuilder extends YiniParserVisitor_1.default {
806
820
  return { key, value: valueNode };
807
821
  };
808
822
  /**
809
- * Visit a parse tree produced by `YiniParser.colon_list_decl`.
810
- * @param ctx the parse tree
811
- * @grammarRule KEY WS? COLON (eol | WS+)* elements (eol | WS+)* eol
812
- * @return the visitor result
823
+ * @note Colon list not supported any more since YINI Spec Package v1.0.0.rc4
813
824
  */
814
- // visitColon_list_decl?: (ctx: ListAfterColonContext) => Result
815
- this.visitColon_list_decl = (ctx) => {
816
- (0, print_1.debugPrint)('-> Entered visitColon_list_decl(..)');
817
- const key = ctx.getChild(0).getText();
818
- (0, print_1.debugPrint)(`visitColon_list_decl(..): key = '${key}'`);
819
- const elems = this.visitElements(ctx.elements());
820
- const value = makeListValue(elems, 'From colon-list');
821
- const current = this.sectionStack[this.sectionStack.length - 1];
822
- // putMember(current, key, list, this.ast, this.onDuplicateKey)
823
- this.putMember(this.errorHandler, ctx, current, key, value,
824
- // this.ast,
825
- this.onDuplicateKey);
826
- (0, print_1.debugPrint)('<- About to exit visitColon_list_decl(..)...');
827
- if ((0, env_1.isDebug)()) {
828
- console.log('List literal: (from a Colon-list)');
829
- (0, print_1.printObject)(value);
830
- }
831
- return value;
832
- };
825
+ // visitColon_list_decl = (ctx: Colon_list_declContext): any => {
826
+ // debugPrint('-> Entered visitColon_list_decl(..)')
827
+ // const key = ctx.getChild(0).getText()
828
+ // debugPrint(`visitColon_list_decl(..): key = '${key}'`)
829
+ // const elems = this.visitElements(ctx.elements())
830
+ // const value = makeListValue(elems, 'From colon-list')
831
+ // const current = this.sectionStack[this.sectionStack.length - 1]
832
+ // // putMember(current, key, list, this.ast, this.onDuplicateKey)
833
+ // this.putMember(
834
+ // this.errorHandler!,
835
+ // ctx,
836
+ // current,
837
+ // key,
838
+ // value,
839
+ // // this.ast,
840
+ // this.onDuplicateKey,
841
+ // )
842
+ // debugPrint('<- About to exit visitColon_list_decl(..)...')
843
+ // if (isDebug()) {
844
+ // console.log('List literal: (from a Colon-list)')
845
+ // printObject(value)
846
+ // }
847
+ // return value
848
+ // }
833
849
  /**
834
850
  * Visit a parse tree produced by `YiniParser.string_concat`.
835
851
  * @param ctx the parse tree
@@ -864,6 +880,7 @@ class ASTBuilder extends YiniParserVisitor_1.default {
864
880
  }
865
881
  catch (err) {
866
882
  const msg = err instanceof Error ? err.message : String(err);
883
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
867
884
  this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Parse error in string', msg);
868
885
  return undefined;
869
886
  }
@@ -921,6 +938,22 @@ class ASTBuilder extends YiniParserVisitor_1.default {
921
938
  };
922
939
  this.sectionStack = [root];
923
940
  }
941
+ // --- Private helper methods --------------------------------
942
+ validateStrictTopLevelStructure() {
943
+ if (!this.isStrict)
944
+ return;
945
+ const numTopLevelSections = this.ast.root.children.length;
946
+ const numTopLevelMembers = this.ast.root.members.size;
947
+ if (numTopLevelMembers > 0) {
948
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
949
+ this.errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Top-level members are not allowed in strict mode.', 'Members were found outside the single required explicit top-level section.', 'Move all top-level members into the explicit top-level section, or parse the document in lenient mode.');
950
+ }
951
+ // Exactly one explicit top-level section in strict mode.
952
+ if (numTopLevelSections !== 1) {
953
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
954
+ this.errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Strict mode requires exactly one explicit top-level section.', `Found ${numTopLevelSections} explicit top-level section${numTopLevelSections === 1 ? '' : 's'}.`, 'Wrap the document in exactly one explicit top-level section and nest any additional sections beneath it.');
955
+ }
956
+ }
924
957
  extractStringParts(tokenText) {
925
958
  // Detect prefix
926
959
  let prefix = '';
@@ -1042,12 +1075,15 @@ class ASTBuilder extends YiniParserVisitor_1.default {
1042
1075
  if (sec.members.has(key)) {
1043
1076
  switch (mode) {
1044
1077
  case 'error':
1078
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
1045
1079
  errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Hit a duplicate key in this section and scope', `Key '${key}' already exists in section '${sec.sectionName}' on level ${sec.level}.`);
1046
1080
  break;
1047
1081
  case 'warn-and-keep-first':
1082
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
1048
1083
  errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Warning', `Hit a duplicate key (will keep first value) in this section and scope`, `Key '${key}' already exists in section '${sec.sectionName}' on level ${sec.level}.`);
1049
1084
  return; // Keep first, don't overwrite.
1050
1085
  case 'warn-and-overwrite':
1086
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
1051
1087
  errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Warning', `Overwrote a duplicate key (will keep last value) in this section and scope`, `Key '${key}' was overwritten in section '${sec.sectionName}' on level ${sec.level}.`);
1052
1088
  break; // Overwrite, replace value.
1053
1089
  case 'keep-first':
@@ -1065,30 +1101,25 @@ class ASTBuilder extends YiniParserVisitor_1.default {
1065
1101
  // Public entry
1066
1102
  buildAST(ctx) {
1067
1103
  this.visitYini?.(ctx);
1068
- // The document terminator is optional by default.
1069
- // If the option `isRequireDocTerminator` is set to true,
1070
- // the '/END' terminator at the end of the document becomes required.
1071
- if (!this.ast.terminatorSeen &&
1072
- this.options.rules.requireDocTerminator === 'required') {
1073
- const msgWhat = `Missing '/END' at end of document (option requireDocTerminator is ${this.options.rules.requireDocTerminator}).`;
1074
- const msgWhy = `The terminator '/END' (case insensitive) is required and must appear at the end of the document.`;
1075
- const msgHint = `This is option can be overriden by the option requireDocTerminator.`;
1104
+ // Strict-mode structural validation.
1105
+ this.validateStrictTopLevelStructure();
1106
+ const isMissingTerminator = !this.ast.terminatorSeen;
1107
+ // In strict mode, the document terminator is required by default.
1108
+ // However, the parse option `requireDocTerminator` is authoritative
1109
+ // and may override that default behavior.
1110
+ const terminatorPolicy = this.options.rules.requireDocTerminator;
1111
+ if (isMissingTerminator && terminatorPolicy === 'required') {
1076
1112
  // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
1077
- this.errorHandler.pushOrBail(undefined, 'Syntax-Error', msgWhat, msgWhy, msgHint);
1113
+ this.errorHandler.pushOrBail(undefined, 'Syntax-Error', "Missing '/END' at end of document.", "The document terminator '/END' (case-insensitive) is required at the end of the document.", "Add '/END' as the final significant line, or change requireDocTerminator to 'optional' or 'warn-if-missing'.");
1078
1114
  }
1079
- else if (!this.ast.terminatorSeen &&
1080
- this.options.rules.requireDocTerminator === 'warn-if-missing') {
1081
- const msgWhat = `Missing '/END' at end of document (option requireDocTerminator is ${this.options.rules.requireDocTerminator}).`;
1082
- const msgWhy = `The terminator '/END' (case insensitive) might be missing at the end of the document.`;
1083
- const msgHint = `This is option can be overriden by the option requireDocTerminator.`;
1115
+ else if (isMissingTerminator &&
1116
+ terminatorPolicy === 'warn-if-missing') {
1084
1117
  // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
1085
- this.errorHandler.pushOrBail(undefined, 'Syntax-Warning', msgWhat, msgWhy, msgHint);
1118
+ this.errorHandler.pushOrBail(undefined, 'Syntax-Warning', "Missing '/END' at end of document.", "The document terminator '/END' (case-insensitive) appears to be missing at the end of the document.", "Add '/END' as the final significant line, or change requireDocTerminator to 'optional'.");
1086
1119
  }
1087
- // Note: Below is important for error checking as well as for meta data.
1088
1120
  this.ast.numOfSections = this.mapSectionNamePaths.size;
1089
1121
  this.ast.numOfMembers = this._numOfMembers;
1090
1122
  if (this.options.isIncludeMeta) {
1091
- // Attach collected meta information.
1092
1123
  this.ast.maxDepth = this.meta_maxLevel;
1093
1124
  this.ast.sectionNamePaths = [...this.mapSectionNamePaths.keys()];
1094
1125
  }