yini-parser 1.5.0 → 1.6.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 (87) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +183 -37
  3. package/dist/YINI.d.ts +22 -7
  4. package/dist/YINI.js +101 -0
  5. package/dist/core/astBuilder.d.ts +94 -15
  6. package/dist/core/astBuilder.js +394 -362
  7. package/dist/core/errorDataHandler.d.ts +6 -1
  8. package/dist/core/errorDataHandler.js +30 -43
  9. package/dist/core/internalTypes.d.ts +10 -1
  10. package/dist/core/objectBuilder.js +21 -6
  11. package/dist/core/options/defaultParserOptions.d.ts +3 -2
  12. package/dist/core/options/defaultParserOptions.js +2 -1
  13. package/dist/core/options/optionsFunctions.js +5 -1
  14. package/dist/core/pipeline/pipeline.js +31 -10
  15. package/dist/core/runtime.js +28 -19
  16. package/dist/grammar/generated/YiniLexer.d.ts +28 -35
  17. package/dist/grammar/generated/YiniLexer.js +323 -310
  18. package/dist/grammar/generated/YiniParser.d.ts +158 -80
  19. package/dist/grammar/generated/YiniParser.js +1141 -620
  20. package/dist/grammar/generated/YiniParserVisitor.d.ts +77 -14
  21. package/dist/grammar/generated/YiniParserVisitor.js +1 -1
  22. package/dist/index.d.ts +2 -1
  23. package/dist/index.js +4 -3
  24. package/dist/parsers/extractHeaderParts.d.ts +12 -19
  25. package/dist/parsers/extractHeaderParts.js +57 -46
  26. package/dist/parsers/parseNumber.d.ts +24 -6
  27. package/dist/parsers/parseNumber.js +114 -49
  28. package/dist/parsers/parseSectionHeader.d.ts +11 -3
  29. package/dist/parsers/parseSectionHeader.js +55 -43
  30. package/dist/parsers/parseString.js +39 -20
  31. package/dist/parsers/validateShebangPlacement.d.ts +3 -0
  32. package/dist/parsers/validateShebangPlacement.js +52 -0
  33. package/dist/types/index.d.ts +19 -2
  34. package/dist/utils/print.d.ts +1 -0
  35. package/dist/utils/print.js +5 -1
  36. package/dist/utils/string.d.ts +1 -0
  37. package/dist/utils/string.js +17 -1
  38. package/dist/utils/system.d.ts +1 -0
  39. package/dist/utils/system.js +6 -1
  40. package/dist/utils/yiniHelpers.d.ts +44 -2
  41. package/dist/utils/yiniHelpers.js +134 -46
  42. package/examples/compare-formats.md +1 -1
  43. package/examples/nested.yini +1 -1
  44. package/package.json +11 -3
  45. package/dist/YINI.js.map +0 -1
  46. package/dist/config/env.js.map +0 -1
  47. package/dist/core/astBuilder.js.map +0 -1
  48. package/dist/core/errorDataHandler.js.map +0 -1
  49. package/dist/core/internalTypes.js.map +0 -1
  50. package/dist/core/objectBuilder.js.map +0 -1
  51. package/dist/core/options/defaultParserOptions.js.map +0 -1
  52. package/dist/core/options/failLevel.js.map +0 -1
  53. package/dist/core/options/optionsFunctions.js.map +0 -1
  54. package/dist/core/parsingRules/modeFromRulesMatcher.js.map +0 -1
  55. package/dist/core/parsingRules/rulesConstAndGuards.js.map +0 -1
  56. package/dist/core/pipeline/errorListeners.js.map +0 -1
  57. package/dist/core/pipeline/pipeline.js.map +0 -1
  58. package/dist/core/resultMetadataBuilder.js.map +0 -1
  59. package/dist/core/runtime.js.map +0 -1
  60. package/dist/dev/main.d.ts +0 -1
  61. package/dist/dev/main.js +0 -139
  62. package/dist/dev/main.js.map +0 -1
  63. package/dist/dev/quick-test-samples/defect-inputs.d.ts +0 -37
  64. package/dist/dev/quick-test-samples/defect-inputs.js +0 -106
  65. package/dist/dev/quick-test-samples/defect-inputs.js.map +0 -1
  66. package/dist/dev/quick-test-samples/valid-inputs.d.ts +0 -21
  67. package/dist/dev/quick-test-samples/valid-inputs.js +0 -422
  68. package/dist/dev/quick-test-samples/valid-inputs.js.map +0 -1
  69. package/dist/grammar/generated/YiniLexer.js.map +0 -1
  70. package/dist/grammar/generated/YiniParser.js.map +0 -1
  71. package/dist/grammar/generated/YiniParserVisitor.js.map +0 -1
  72. package/dist/index.js.map +0 -1
  73. package/dist/parsers/extractHeaderParts.js.map +0 -1
  74. package/dist/parsers/extractSignificantYiniLine.js.map +0 -1
  75. package/dist/parsers/parseBoolean.js.map +0 -1
  76. package/dist/parsers/parseNull.js.map +0 -1
  77. package/dist/parsers/parseNumber.js.map +0 -1
  78. package/dist/parsers/parseSectionHeader.js.map +0 -1
  79. package/dist/parsers/parseString.js.map +0 -1
  80. package/dist/types/index.js.map +0 -1
  81. package/dist/utils/number.js.map +0 -1
  82. package/dist/utils/object.js.map +0 -1
  83. package/dist/utils/pathAndFileName.js.map +0 -1
  84. package/dist/utils/print.js.map +0 -1
  85. package/dist/utils/string.js.map +0 -1
  86. package/dist/utils/system.js.map +0 -1
  87. package/dist/utils/yiniHelpers.js.map +0 -1
@@ -8,6 +8,7 @@ export declare class ErrorDataHandler {
8
8
  private readonly subjectType;
9
9
  private readonly fileName;
10
10
  private readonly persistThreshold;
11
+ private readonly isDiagnosticOutputEnabled;
11
12
  private readonly isQuiet;
12
13
  private readonly isSilent;
13
14
  private readonly isThrowOnError;
@@ -30,7 +31,8 @@ export declare class ErrorDataHandler {
30
31
  - Level 1 = abort on errors only
31
32
  - Level 2 = abort even on warnings
32
33
  */
33
- constructor(subjectType: TSubjectType, fileName?: string | undefined, bailSensitivityLevel?: TBailSensitivityLevel, isQuiet?: boolean, // Reduce output (show only errors, does not effect warnings and etc. in meta data).
34
+ constructor(subjectType: TSubjectType, fileName?: string | undefined, bailSensitivityLevel?: TBailSensitivityLevel, isDiagnosticOutputEnabled?: boolean, // Opt in to writing diagnostics to stderr.
35
+ isQuiet?: boolean, // When diagnostic output is enabled, show errors only.
34
36
  isSilent?: boolean, // Suppress all output (even errors, exit code only).
35
37
  isThrowOnError?: boolean);
36
38
  private makeIssue;
@@ -50,6 +52,9 @@ export declare class ErrorDataHandler {
50
52
  */
51
53
  pushOrBail(locInput: IErrorLocationInput | undefined, type: TIssueType, msgWhat: string, msgWhy?: string, msgHint?: string): void;
52
54
  private formatSignificantMessageLine;
55
+ private shouldEmitDiagnostics;
56
+ private shouldEmitNonErrors;
57
+ private emitDiagnosticToStderr;
53
58
  private emitFatalError;
54
59
  private emitInternalError;
55
60
  private emitSyntaxError;
@@ -42,7 +42,8 @@ class ErrorDataHandler {
42
42
  - Level 1 = abort on errors only
43
43
  - Level 2 = abort even on warnings
44
44
  */
45
- constructor(subjectType, fileName = undefined, bailSensitivityLevel = '1-Abort-on-Errors', isQuiet = false, // Reduce output (show only errors, does not effect warnings and etc. in meta data).
45
+ constructor(subjectType, fileName = undefined, bailSensitivityLevel = '1-Abort-on-Errors', isDiagnosticOutputEnabled = false, // Opt in to writing diagnostics to stderr.
46
+ isQuiet = false, // When diagnostic output is enabled, show errors only.
46
47
  isSilent = false, // Suppress all output (even errors, exit code only).
47
48
  isThrowOnError = false) {
48
49
  this.errors = [];
@@ -58,6 +59,7 @@ class ErrorDataHandler {
58
59
  this.subjectType = subjectType;
59
60
  this.fileName = fileName;
60
61
  this.persistThreshold = bailSensitivityLevel;
62
+ this.isDiagnosticOutputEnabled = isDiagnosticOutputEnabled;
61
63
  this.isQuiet = isQuiet;
62
64
  this.isSilent = isSilent;
63
65
  this.isThrowOnError = isThrowOnError;
@@ -118,8 +120,8 @@ class ErrorDataHandler {
118
120
  lineNum: lineNum || 0, // 1-based, if n/a use 0.
119
121
  colNum: colNum || 0, // 1-based, if n/a use 0.
120
122
  };
121
- if (!this.isSilent) {
122
- console.log(); // Print an empty line before outputting message.
123
+ if (this.shouldEmitDiagnostics()) {
124
+ console.error(); // Print an empty line before outputting message.
123
125
  }
124
126
  switch (type) {
125
127
  case 'Internal-Error':
@@ -229,68 +231,53 @@ class ErrorDataHandler {
229
231
  }
230
232
  }
231
233
  }
232
- /*
233
- * - error/warning → console.error / console.warn
234
- * - notice/info → console.log / console.info
235
- */
234
+ shouldEmitDiagnostics() {
235
+ return this.isDiagnosticOutputEnabled && !this.isSilent;
236
+ }
237
+ shouldEmitNonErrors() {
238
+ return this.shouldEmitDiagnostics() && !this.isQuiet;
239
+ }
240
+ emitDiagnosticToStderr(write, messageHeader, msgWhat = '', msgWhy = '', msgHint = '') {
241
+ write(messageHeader);
242
+ msgWhat && write(msgWhat);
243
+ msgWhy && write(msgWhy);
244
+ msgHint && write(msgHint);
245
+ write();
246
+ }
236
247
  emitFatalError(loc, msgWhat = 'Something went wrong!', msgWhy = '', msgHint = '') {
237
248
  const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[0]);
238
- if (!this.isSilent) {
239
- console.error(messageHeader); // Print the issue title.
240
- msgWhat && console.log(msgWhat);
241
- msgWhy && console.log(msgWhy);
242
- msgHint && console.log(msgHint);
243
- console.log(); // Emit an empty line before outputting message.
249
+ if (this.shouldEmitDiagnostics()) {
250
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
244
251
  }
245
252
  }
246
253
  emitInternalError(loc, msgWhat = 'Something went wrong!', msgWhy = '', msgHint = '') {
247
254
  const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[1]);
248
- if (!this.isSilent) {
249
- console.error(messageHeader); // Print the issue title.
250
- msgWhat && console.log(msgWhat);
251
- msgWhy && console.log(msgWhy);
252
- msgHint && console.log(msgHint);
253
- console.log(); // Emit an empty line before outputting message.
255
+ if (this.shouldEmitDiagnostics()) {
256
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
254
257
  }
255
258
  }
256
259
  emitSyntaxError(loc, msgWhat, msgWhy = '', msgHint = '') {
257
260
  const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[2]);
258
- if (!this.isSilent) {
259
- console.error(messageHeader); // Print the issue title.
260
- msgWhat && console.log(msgWhat);
261
- msgWhy && console.log(msgWhy);
262
- msgHint && console.log(msgHint);
263
- console.log(); // Emit an empty line before outputting message.
261
+ if (this.shouldEmitDiagnostics()) {
262
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
264
263
  }
265
264
  }
266
265
  emitSyntaxWarning(loc, msgWhat, msgWhy = '', msgHint = '') {
267
266
  const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[3]);
268
- if (!this.isQuiet && !this.isSilent) {
269
- console.warn(messageHeader); // Print the issue title.
270
- msgWhat && console.log(msgWhat);
271
- msgWhy && console.log(msgWhy);
272
- msgHint && console.log(msgHint);
273
- console.log(); // Emit an empty line before outputting message.
267
+ if (this.shouldEmitNonErrors()) {
268
+ this.emitDiagnosticToStderr(console.warn.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
274
269
  }
275
270
  }
276
271
  emitNotice(loc, msgWhat, msgWhy = '', msgHint = '') {
277
272
  const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[4]);
278
- if (!this.isQuiet && !this.isSilent) {
279
- console.log(messageHeader); // Print the issue title.
280
- msgWhat && console.log(msgWhat);
281
- msgWhy && console.log(msgWhy);
282
- msgHint && console.log(msgHint);
283
- console.log(); // Emit an empty line before outputting message.
273
+ if (this.shouldEmitNonErrors()) {
274
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
284
275
  }
285
276
  }
286
277
  emitInfo(loc, msgWhat, msgWhy = '', msgHint = '') {
287
278
  const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[5]);
288
- if (!this.isQuiet && !this.isSilent) {
289
- console.info(messageHeader); // Print the issue title.
290
- msgWhat && console.log(msgWhat);
291
- msgWhy && console.log(msgWhy);
292
- msgHint && console.log(msgHint);
293
- console.log(); // Emit an empty line before outputting message.
279
+ if (this.shouldEmitNonErrors()) {
280
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
294
281
  }
295
282
  }
296
283
  getNumOfAllMessages() {
@@ -70,7 +70,7 @@ export type TObjectValue = {
70
70
  entries: Readonly<Record<string, TValueLiteral>>;
71
71
  tag: string | undefined;
72
72
  };
73
- type TStringKind = 'raw' | 'classic' | 'hyper' | 'triple-raw' | 'triple-classic';
73
+ type TStringKind = 'raw' | 'classic' | 'triple-raw' | 'triple-classic';
74
74
  export interface IParsedStringInput {
75
75
  strKind: TStringKind;
76
76
  value: string;
@@ -82,6 +82,13 @@ export interface IErrorLocationInput {
82
82
  endColumn?: number;
83
83
  }
84
84
  export type TIssueType = 'Fatal-Error' | 'Internal-Error' | 'Syntax-Error' | 'Syntax-Warning' | 'Notice' | 'Info';
85
+ export interface IPreflightIssue {
86
+ locInput: IErrorLocationInput | undefined;
87
+ type: TIssueType;
88
+ msgWhat: string;
89
+ msgWhy?: string;
90
+ msgHint?: string;
91
+ }
85
92
  interface IMetaBaseInfo {
86
93
  sourceType: TSourceType;
87
94
  fileName: string | undefined;
@@ -96,6 +103,7 @@ export interface IRuntimeInfo extends IMetaBaseInfo {
96
103
  timeIoMs: number | null;
97
104
  preferredBailSensitivity: null | PreferredFailLevel;
98
105
  sha256: string | null;
106
+ preflightIssues: IPreflightIssue[];
99
107
  }
100
108
  export interface IParseCoreOptions {
101
109
  rules: IParseRuleOptions;
@@ -104,6 +112,7 @@ export interface IParseCoreOptions {
104
112
  isWithDiagnostics: boolean;
105
113
  isWithTiming: boolean;
106
114
  isKeepUndefinedInMeta: boolean;
115
+ isDiagnosticOutputEnabled: boolean;
107
116
  isQuiet: boolean;
108
117
  isSilent: boolean;
109
118
  isThrowOnError: boolean;
@@ -17,6 +17,7 @@ const print_1 = require("../utils/print");
17
17
  *
18
18
  * @note All `tag` fields MUST be ignored.
19
19
  */
20
+ // src/core/objectBuilder.ts
20
21
  const astToObject = (ast, errorHandler) => {
21
22
  (0, print_1.debugPrint)('-> constructFinalObject(..)');
22
23
  const out = {};
@@ -28,12 +29,12 @@ const astToObject = (ast, errorHandler) => {
28
29
  }
29
30
  // 2) Mount explicit top-level sections directly onto result.
30
31
  for (const child of ast.root.children) {
31
- // Collision: a lenient orphan member already used this name.
32
32
  if (Object.prototype.hasOwnProperty.call(out, child.sectionName)) {
33
33
  errorHandler.pushOrBail(undefined, 'Syntax-Error', `Name collision between orphan member and section '${child.sectionName}'`, `The name '${child.sectionName}' is already used by a top-level member outside any explicit section and cannot also be used as a top-level section name.`, `Rename either the orphan member or the section to avoid the collision.`);
34
34
  continue;
35
35
  }
36
- define(out, child.sectionName, sectionToObject(child));
36
+ // define(out, child.sectionName, sectionToObject(child))
37
+ define(out, child.sectionName, sectionToObject(child, errorHandler));
37
38
  }
38
39
  return out;
39
40
  };
@@ -42,15 +43,29 @@ exports.astToObject = astToObject;
42
43
  * Convert a section (members + nested sections) to a plain object,
43
44
  * preserving insertion order.
44
45
  */
45
- const sectionToObject = (node) => {
46
+ // const sectionToObject = (node: IYiniSection): Record<string, unknown> => {
47
+ // const obj: Record<string, unknown> = {}
48
+ // // 1) Members (Map preserves insertion order).
49
+ // for (const [key, val] of node.members) {
50
+ // define(obj, key, literalToJS(val))
51
+ // }
52
+ // // 2) Nested sections (array order preserved).
53
+ // for (const child of node.children) {
54
+ // define(obj, child.sectionName, sectionToObject(child))
55
+ // }
56
+ // return obj
57
+ // }
58
+ const sectionToObject = (node, errorHandler) => {
46
59
  const obj = {};
47
- // 1) Members (Map preserves insertion order).
48
60
  for (const [key, val] of node.members) {
49
61
  define(obj, key, literalToJS(val));
50
62
  }
51
- // 2) Nested sections (array order preserved).
52
63
  for (const child of node.children) {
53
- define(obj, child.sectionName, sectionToObject(child));
64
+ if (Object.prototype.hasOwnProperty.call(obj, child.sectionName)) {
65
+ errorHandler.pushOrBail(undefined, 'Syntax-Error', `Name collision between member and subsection '${child.sectionName}'`, `The name '${child.sectionName}' is already used by a member in section '${node.sectionName}' and cannot also be used as a subsection name.`, 'Rename either the member or the subsection.');
66
+ continue;
67
+ }
68
+ define(obj, child.sectionName, sectionToObject(child, errorHandler));
54
69
  }
55
70
  return obj;
56
71
  };
@@ -7,11 +7,12 @@ export declare const getDefaultUserOptions: (mode: TParserMode) => {
7
7
  onDuplicateKey: import("../../types").OnDuplicateKey;
8
8
  requireDocTerminator: import("../../types").DocumentTerminatorRule;
9
9
  treatEmptyValueAsNull: import("../../types").EmptyValueRule;
10
+ logDiagnostics: boolean;
10
11
  quiet: boolean;
11
12
  silent: boolean;
12
13
  throwOnError: boolean;
13
- strictMode: boolean;
14
14
  failLevel: import("../../types").PreferredFailLevel;
15
15
  includeMetadata: boolean;
16
+ strictMode: boolean;
16
17
  };
17
- export type TNormalizedUserOptions = Required<Pick<ParseOptions, 'strictMode' | 'failLevel' | 'includeMetadata' | 'includeDiagnostics' | 'includeTiming' | 'preserveUndefinedInMeta' | 'onDuplicateKey' | 'requireDocTerminator' | 'treatEmptyValueAsNull' | 'quiet' | 'silent' | 'throwOnError'>>;
18
+ export type TNormalizedUserOptions = Required<Pick<ParseOptions, 'strictMode' | 'failLevel' | 'includeMetadata' | 'includeDiagnostics' | 'includeTiming' | 'preserveUndefinedInMeta' | 'onDuplicateKey' | 'requireDocTerminator' | 'treatEmptyValueAsNull' | 'logDiagnostics' | 'quiet' | 'silent' | 'throwOnError'>>;
@@ -14,7 +14,8 @@ const BASE_DEFAULTS = {
14
14
  onDuplicateKey: 'error',
15
15
  requireDocTerminator: 'optional',
16
16
  treatEmptyValueAsNull: 'allow-with-warning',
17
- quiet: false, // Suppress warnings in console (does not affect warnings in meta data).
17
+ logDiagnostics: false,
18
+ quiet: false, // When logDiagnostics is true, suppress warnings in stderr output.
18
19
  silent: false,
19
20
  //@todo: Change default throwOnError to false
20
21
  throwOnError: true, // Will throw on first parse error encountered.
@@ -15,13 +15,16 @@ const toCoreOptions = (userOpts, bailLevel) => {
15
15
  treatEmptyValueAsNull: userOpts.treatEmptyValueAsNull,
16
16
  },
17
17
  bailSensitivity: level,
18
- isIncludeMeta: userOpts.includeMetadata,
18
+ isIncludeMeta: userOpts.includeMetadata ||
19
+ userOpts.includeDiagnostics ||
20
+ userOpts.includeTiming,
19
21
  // isWithDiagnostics: isDev() || isDebug() || userOpts.includeDiagnostics,
20
22
  isWithDiagnostics: userOpts.includeDiagnostics,
21
23
  // isWithTiming: isDev() || isDebug() || userOpts.includeTiming,
22
24
  isWithTiming: userOpts.includeTiming,
23
25
  // isKeepUndefinedInMeta: isDebug() || userOpts.preserveUndefinedInMeta,
24
26
  isKeepUndefinedInMeta: userOpts.preserveUndefinedInMeta,
27
+ isDiagnosticOutputEnabled: userOpts.logDiagnostics,
25
28
  isQuiet: userOpts.quiet, // Suppress warnings, etc.
26
29
  isSilent: userOpts.silent,
27
30
  isThrowOnError: userOpts.throwOnError,
@@ -41,6 +44,7 @@ const isOptionsObjectForm = (v) => {
41
44
  'onDuplicateKey' in v ||
42
45
  'requireDocTerminator' in v ||
43
46
  'treatEmptyValueAsNull' in v ||
47
+ 'logDiagnostics' in v ||
44
48
  'quiet' in v ||
45
49
  'silent' in v ||
46
50
  'throwOnError' in v));
@@ -63,12 +63,21 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
63
63
  // default:
64
64
  // persistThreshold = '2-Abort-Even-on-Warnings'
65
65
  // }
66
- const errorHandler = new errorDataHandler_1.ErrorDataHandler(runtimeInfo.sourceType, runtimeInfo.fileName, coreOptions.bailSensitivity, coreOptions.isQuiet, coreOptions.isSilent, coreOptions.isThrowOnError);
67
- if (yiniContent.trim() === '') {
68
- const isFileSourceType = runtimeInfo?.sourceType === 'File';
69
- // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
70
- errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Empty YINI document.', `The input is blank or contains only whitespace in the ${isFileSourceType ? 'YINI file' : 'YINI inline content'}.`, `Tip: Add at least one section '^ SectionName' or a key-value pair 'key = value' to make it a valid YINI file.`);
66
+ const errorHandler = new errorDataHandler_1.ErrorDataHandler(runtimeInfo.sourceType, runtimeInfo.fileName, coreOptions.bailSensitivity, coreOptions.isDiagnosticOutputEnabled, coreOptions.isQuiet, coreOptions.isSilent, coreOptions.isThrowOnError);
67
+ for (const issue of runtimeInfo.preflightIssues) {
68
+ errorHandler.pushOrBail(issue.locInput, issue.type, issue.msgWhat, issue.msgWhy, issue.msgHint);
71
69
  }
70
+ // if (yiniContent.trim() === '') {
71
+ // const isFileSourceType: boolean = runtimeInfo?.sourceType === 'File'
72
+ // // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
73
+ // errorHandler.pushOrBail(
74
+ // undefined,
75
+ // 'Syntax-Error',
76
+ // 'Empty YINI document.',
77
+ // `The input is blank or contains only whitespace in the ${isFileSourceType ? 'YINI file' : 'YINI inline content'}.`,
78
+ // `Tip: Add at least one section '^ SectionName' or a key-value pair 'key = value' to make it a valid YINI file.`,
79
+ // )
80
+ // }
72
81
  //---------------------------------------------
73
82
  // Note: Only computed when isWithTiming.
74
83
  let timeStartMs = 0;
@@ -140,8 +149,18 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
140
149
  const builder = new astBuilder_1.default(errorHandler, coreOptions, runtimeInfo.sourceType, runtimeInfo.fileName || null);
141
150
  const ast = builder.buildAST(parseTree);
142
151
  if (ast.numOfMembers === 0 && ast.numOfSections === 0) {
143
- // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
144
- errorHandler.pushOrBail(undefined, 'Syntax-Error', 'No meaningful content.', `No sections or members found in the ${ast.sourceType === 'File' ? 'YINI file' : 'YINI inline content'}.`, `${ast.sourceType === 'File' ? 'A valid YINI file' : 'Any valid YINI inline content'} must contain at least one section '^ SectionName' or a key–value pair 'key = value' to make it a valid YINI file.`);
152
+ // Lenient mode: empty document is allowed, but warning.
153
+ // Strict mode: empty document is invalid, error.
154
+ const sourceLabel = ast.sourceType === 'File' ? 'YINI file' : 'YINI inline content';
155
+ const sourceRequirement = ast.sourceType === 'File'
156
+ ? 'A valid YINI file must contain at least one section, for example: ^ SectionName, or a key-value pair, for example: key = value.'
157
+ : 'Valid YINI inline content must contain at least one section, for example: ^ SectionName, or a key-value pair, for example: key = value.';
158
+ if (ast.isStrict === true) {
159
+ errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Empty YINI document.', `The ${sourceLabel} contains no meaningful content.`, sourceRequirement);
160
+ }
161
+ else {
162
+ errorHandler.pushOrBail(undefined, 'Syntax-Warning', 'Empty YINI document.', `The ${sourceLabel} contains no meaningful content.`, sourceRequirement);
163
+ }
145
164
  }
146
165
  if ((0, env_1.isDebug)()) {
147
166
  console.log();
@@ -180,8 +199,9 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
180
199
  // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
181
200
  errorHandler.pushOrBail(undefined, 'Syntax-Warning', 'Warning: Strict initialMode is not yet fully implemented.', 'Some validation rules may still be missing or incomplete.');
182
201
  if (coreOptions.bailSensitivity === '0-Ignore-Errors') {
183
- // IMPORTANT: If "silent" option is set, do not log anything to console!
184
- if (!coreOptions.isQuiet && !coreOptions.isSilent) {
202
+ if (coreOptions.isDiagnosticOutputEnabled &&
203
+ !coreOptions.isQuiet &&
204
+ !coreOptions.isSilent) {
185
205
  console.warn(`Warning: The initial mode was set to strict mode, but fail level is set to 'ignore-errors'. This combination is contradictory and might be a mistake.`);
186
206
  }
187
207
  }
@@ -208,7 +228,8 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
208
228
  const constructedMetadata = (0, resultMetadataBuilder_1.buildResultMetadata)(params);
209
229
  (0, print_1.debugPrint)('getNumOfErrors(): ' + errorHandler.getNumOfErrors());
210
230
  // Print a summary line at the end if any errors or warnings.
211
- if (!coreOptions.isQuiet && !coreOptions.isSilent) {
231
+ if (coreOptions.isDiagnosticOutputEnabled &&
232
+ !coreOptions.isSilent) {
212
233
  const errors = errorHandler.getNumOfErrors();
213
234
  const warnings = errorHandler.getNumOfWarnings();
214
235
  // Notes:
@@ -16,8 +16,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
16
16
  var _YiniRuntime_runtime;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.YiniRuntime = void 0;
19
+ // src/core/runtime.ts
19
20
  const fs_1 = __importDefault(require("fs"));
20
21
  const env_1 = require("../config/env");
22
+ const validateShebangPlacement_1 = require("../parsers/validateShebangPlacement");
21
23
  const pathAndFileName_1 = require("../utils/pathAndFileName");
22
24
  const print_1 = require("../utils/print");
23
25
  const string_1 = require("../utils/string");
@@ -54,6 +56,7 @@ class YiniRuntime {
54
56
  timeIoMs: null,
55
57
  preferredBailSensitivity: null,
56
58
  sha256: null,
59
+ preflightIssues: [],
57
60
  };
58
61
  }
59
62
  // --- Single implementation --------------------------------------------
@@ -102,6 +105,19 @@ class YiniRuntime {
102
105
  includeMetadata,
103
106
  };
104
107
  }
108
+ const shebangIssue = (0, validateShebangPlacement_1.getShebangPlacementIssue)(yiniContent, userOpts.strictMode);
109
+ if (shebangIssue) {
110
+ __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").preflightIssues.push(shebangIssue);
111
+ }
112
+ const originalContent = yiniContent;
113
+ yiniContent = (0, validateShebangPlacement_1.stripBomAndValidShebang)(yiniContent);
114
+ if (originalContent.startsWith('\uFEFF')) {
115
+ (0, print_1.devPrint)('runParse(..): BOM was detected and stripped from UTF-8 content.');
116
+ }
117
+ if (originalContent.startsWith('#!') ||
118
+ originalContent.startsWith('\uFEFF#!')) {
119
+ (0, print_1.devPrint)('runParse(..): Shebang detected on first line and ignored.');
120
+ }
105
121
  if (userOpts.includeMetadata && __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").sourceType === 'Inline') {
106
122
  const lineCount = yiniContent.split(/\r?\n/).length; // Counts the lines.
107
123
  const sha256 = (0, string_1.computeSha256)(yiniContent); // NOTE: Compute BEFORE any possible tampering of content.
@@ -111,9 +127,13 @@ class YiniRuntime {
111
127
  }
112
128
  // NOTE: Important: Do not trim or mutate the yiniContent here, due
113
129
  // to it will mess up the line numbers in error reporting.
114
- if (!yiniContent) {
115
- throw new Error('Syntax-Error: Unexpected blank YINI input');
130
+ // if (!yiniContent) {
131
+ // throw new Error('Syntax-Error: Unexpected blank YINI input')
132
+ // }
133
+ if (yiniContent === null || yiniContent === undefined) {
134
+ throw new Error('Syntax-Error: Missing YINI input');
116
135
  }
136
+ // IMPORTANT: Makes sure input ends with an empty NL!
117
137
  if (!yiniContent.endsWith('\n')) {
118
138
  yiniContent += '\n';
119
139
  }
@@ -166,13 +186,6 @@ class YiniRuntime {
166
186
  };
167
187
  }
168
188
  if ((0, pathAndFileName_1.getFileNameExtension)(filePath).toLowerCase() !== '.yini') {
169
- // IMPORTANT: If "silent" option is set, do not log anything to console!
170
- if (!userOpts.silent) {
171
- // In quiet-mode we still show errors (these are fine).
172
- console.error('Invalid file extension for YINI file:');
173
- console.error(`"${filePath}"`);
174
- console.error('File does not have a valid ".yini" extension (case-insensitive).');
175
- }
176
189
  throw new Error('Error: Unexpected file extension for YINI file');
177
190
  }
178
191
  // ---- Phase 0: I/O ----
@@ -192,22 +205,18 @@ class YiniRuntime {
192
205
  __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").preferredBailSensitivity = userOpts.failLevel;
193
206
  __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").sha256 = (0, string_1.computeSha256)(content); // NOTE: Compute BEFORE any possible tampering of content.
194
207
  }
195
- let hasNoNewlineAtEOF = false;
196
208
  if (!content.endsWith('\n')) {
209
+ __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").preflightIssues.push({
210
+ locInput: undefined,
211
+ type: 'Syntax-Warning',
212
+ msgWhat: 'No newline at end of file.',
213
+ msgWhy: `It's recommended to end a YINI file with a newline. File: "${filePath}"`,
214
+ });
197
215
  content += '\n';
198
- hasNoNewlineAtEOF = true;
199
216
  }
200
217
  const result = this.runParse(content, {
201
218
  ...userOpts,
202
219
  });
203
- // if (hasNoNewlineAtEOF && !userOpts.quiet && !userOpts.silent) {
204
- if (hasNoNewlineAtEOF && !userOpts.quiet) {
205
- // IMPORTANT: If "silent" option is set, do not log anything to console!
206
- if (!userOpts.silent) {
207
- //@todo: (or maybe not, 20250917) Maybe let errorHandler emit message
208
- console.warn(`No newline at end of file, it's recommended to end a file with a newline. File:\n"${filePath}"`);
209
- }
210
- }
211
220
  return result;
212
221
  }
213
222
  }
@@ -1,4 +1,4 @@
1
- import { ATN, CharStream, DFA, Lexer, RuleContext } from "antlr4";
1
+ import { ATN, CharStream, DFA, Lexer } from "antlr4";
2
2
  export default class YiniLexer extends Lexer {
3
3
  static readonly SHEBANG = 1;
4
4
  static readonly YINI_TOKEN = 2;
@@ -13,44 +13,39 @@ export default class YiniLexer extends Lexer {
13
13
  static readonly EMPTY_OBJECT = 11;
14
14
  static readonly EMPTY_LIST = 12;
15
15
  static readonly STRING = 13;
16
- static readonly TRIPLE_QUOTED_STRING = 14;
17
- static readonly SINGLE_OR_DOUBLE = 15;
18
- static readonly R_AND_C_STRING = 16;
19
- static readonly HYPER_STRING = 17;
20
- static readonly NUMBER = 18;
21
- static readonly SS = 19;
22
- static readonly CARET = 20;
23
- static readonly GT = 21;
24
- static readonly LT = 22;
25
- static readonly EQ = 23;
26
- static readonly HASH = 24;
27
- static readonly COMMA = 25;
28
- static readonly COLON = 26;
29
- static readonly OB = 27;
30
- static readonly CB = 28;
31
- static readonly OC = 29;
32
- static readonly CC = 30;
33
- static readonly PLUS = 31;
34
- static readonly DOLLAR = 32;
35
- static readonly PC = 33;
36
- static readonly AT = 34;
37
- static readonly SEMICOLON = 35;
38
- static readonly NL = 36;
39
- static readonly WS = 37;
40
- static readonly BLOCK_COMMENT = 38;
41
- static readonly LINE_COMMENT = 39;
42
- static readonly INLINE_COMMENT = 40;
43
- static readonly KEY = 41;
44
- static readonly IDENT_INVALID = 42;
45
- static readonly REST = 43;
46
- static readonly META_INVALID = 44;
16
+ static readonly NUMBER = 14;
17
+ static readonly CARET = 15;
18
+ static readonly SS = 16;
19
+ static readonly GT = 17;
20
+ static readonly LT = 18;
21
+ static readonly EQ = 19;
22
+ static readonly COMMA = 20;
23
+ static readonly COLON = 21;
24
+ static readonly OB = 22;
25
+ static readonly CB = 23;
26
+ static readonly OC = 24;
27
+ static readonly CC = 25;
28
+ static readonly PLUS = 26;
29
+ static readonly DOLLAR = 27;
30
+ static readonly PC = 28;
31
+ static readonly AT = 29;
32
+ static readonly SEMICOLON = 30;
33
+ static readonly NL = 31;
34
+ static readonly WS = 32;
35
+ static readonly BLOCK_COMMENT = 33;
36
+ static readonly DISABLED_LINE = 34;
37
+ static readonly FULL_LINE_COMMENT = 35;
38
+ static readonly INLINE_COMMENT = 36;
39
+ static readonly KEY = 37;
40
+ static readonly IDENT_INVALID = 38;
41
+ static readonly REST = 39;
42
+ static readonly META_INVALID = 40;
47
43
  static readonly EOF: number;
48
44
  static readonly channelNames: string[];
49
45
  static readonly literalNames: (string | null)[];
50
46
  static readonly symbolicNames: (string | null)[];
51
47
  static readonly modeNames: string[];
52
48
  static readonly ruleNames: string[];
53
- atLineStart(): boolean;
54
49
  constructor(input: CharStream);
55
50
  get grammarFileName(): string;
56
51
  get literalNames(): (string | null)[];
@@ -59,8 +54,6 @@ export default class YiniLexer extends Lexer {
59
54
  get serializedATN(): number[];
60
55
  get channelNames(): string[];
61
56
  get modeNames(): string[];
62
- sempred(localctx: RuleContext, ruleIndex: number, predIndex: number): boolean;
63
- private LINE_COMMENT_sempred;
64
57
  static readonly _serializedATN: number[];
65
58
  private static __ATN;
66
59
  static get _ATN(): ATN;