yini-parser 1.5.0 → 1.6.1

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 (90) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +1 -1
  3. package/README.md +199 -37
  4. package/dist/YINI.d.ts +22 -7
  5. package/dist/YINI.js +104 -0
  6. package/dist/core/astBuilder.d.ts +94 -15
  7. package/dist/core/astBuilder.js +396 -364
  8. package/dist/core/errorDataHandler.d.ts +29 -1
  9. package/dist/core/errorDataHandler.js +120 -63
  10. package/dist/core/internalTypes.d.ts +10 -1
  11. package/dist/core/objectBuilder.js +21 -6
  12. package/dist/core/options/defaultParserOptions.d.ts +3 -2
  13. package/dist/core/options/defaultParserOptions.js +2 -1
  14. package/dist/core/options/optionsFunctions.js +5 -1
  15. package/dist/core/pipeline/pipeline.js +31 -12
  16. package/dist/core/runtime.js +29 -34
  17. package/dist/grammar/generated/YiniLexer.d.ts +28 -35
  18. package/dist/grammar/generated/YiniLexer.js +323 -310
  19. package/dist/grammar/generated/YiniParser.d.ts +158 -80
  20. package/dist/grammar/generated/YiniParser.js +1141 -620
  21. package/dist/grammar/generated/YiniParserVisitor.d.ts +77 -14
  22. package/dist/grammar/generated/YiniParserVisitor.js +1 -1
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.js +6 -3
  25. package/dist/parsers/extractHeaderParts.d.ts +12 -19
  26. package/dist/parsers/extractHeaderParts.js +57 -46
  27. package/dist/parsers/parseNumber.d.ts +24 -6
  28. package/dist/parsers/parseNumber.js +114 -49
  29. package/dist/parsers/parseSectionHeader.d.ts +11 -3
  30. package/dist/parsers/parseSectionHeader.js +55 -43
  31. package/dist/parsers/parseString.js +42 -21
  32. package/dist/parsers/validateShebangPlacement.d.ts +4 -0
  33. package/dist/parsers/validateShebangPlacement.js +115 -0
  34. package/dist/types/index.d.ts +19 -2
  35. package/dist/utils/print.d.ts +1 -0
  36. package/dist/utils/print.js +5 -1
  37. package/dist/utils/string.d.ts +1 -0
  38. package/dist/utils/string.js +17 -1
  39. package/dist/utils/system.d.ts +1 -0
  40. package/dist/utils/system.js +6 -1
  41. package/dist/utils/yiniHelpers.d.ts +44 -2
  42. package/dist/utils/yiniHelpers.js +134 -46
  43. package/examples/basic.yini +1 -0
  44. package/examples/compare-formats.md +1 -1
  45. package/examples/nested.yini +1 -1
  46. package/examples/parse-example.ts +1 -0
  47. package/package.json +11 -3
  48. package/dist/YINI.js.map +0 -1
  49. package/dist/config/env.js.map +0 -1
  50. package/dist/core/astBuilder.js.map +0 -1
  51. package/dist/core/errorDataHandler.js.map +0 -1
  52. package/dist/core/internalTypes.js.map +0 -1
  53. package/dist/core/objectBuilder.js.map +0 -1
  54. package/dist/core/options/defaultParserOptions.js.map +0 -1
  55. package/dist/core/options/failLevel.js.map +0 -1
  56. package/dist/core/options/optionsFunctions.js.map +0 -1
  57. package/dist/core/parsingRules/modeFromRulesMatcher.js.map +0 -1
  58. package/dist/core/parsingRules/rulesConstAndGuards.js.map +0 -1
  59. package/dist/core/pipeline/errorListeners.js.map +0 -1
  60. package/dist/core/pipeline/pipeline.js.map +0 -1
  61. package/dist/core/resultMetadataBuilder.js.map +0 -1
  62. package/dist/core/runtime.js.map +0 -1
  63. package/dist/dev/main.d.ts +0 -1
  64. package/dist/dev/main.js +0 -139
  65. package/dist/dev/main.js.map +0 -1
  66. package/dist/dev/quick-test-samples/defect-inputs.d.ts +0 -37
  67. package/dist/dev/quick-test-samples/defect-inputs.js +0 -106
  68. package/dist/dev/quick-test-samples/defect-inputs.js.map +0 -1
  69. package/dist/dev/quick-test-samples/valid-inputs.d.ts +0 -21
  70. package/dist/dev/quick-test-samples/valid-inputs.js +0 -422
  71. package/dist/dev/quick-test-samples/valid-inputs.js.map +0 -1
  72. package/dist/grammar/generated/YiniLexer.js.map +0 -1
  73. package/dist/grammar/generated/YiniParser.js.map +0 -1
  74. package/dist/grammar/generated/YiniParserVisitor.js.map +0 -1
  75. package/dist/index.js.map +0 -1
  76. package/dist/parsers/extractHeaderParts.js.map +0 -1
  77. package/dist/parsers/extractSignificantYiniLine.js.map +0 -1
  78. package/dist/parsers/parseBoolean.js.map +0 -1
  79. package/dist/parsers/parseNull.js.map +0 -1
  80. package/dist/parsers/parseNumber.js.map +0 -1
  81. package/dist/parsers/parseSectionHeader.js.map +0 -1
  82. package/dist/parsers/parseString.js.map +0 -1
  83. package/dist/types/index.js.map +0 -1
  84. package/dist/utils/number.js.map +0 -1
  85. package/dist/utils/object.js.map +0 -1
  86. package/dist/utils/pathAndFileName.js.map +0 -1
  87. package/dist/utils/print.js.map +0 -1
  88. package/dist/utils/string.js.map +0 -1
  89. package/dist/utils/system.js.map +0 -1
  90. package/dist/utils/yiniHelpers.js.map +0 -1
@@ -1,6 +1,28 @@
1
1
  import { IssuePayload } from '../types';
2
2
  import { IErrorLocationInput, TBailSensitivityLevel, TIssueType, TSubjectType } from './internalTypes';
3
3
  export declare const toErrorLocation: (ctx: any) => IErrorLocationInput | undefined;
4
+ export interface YiniParseErrorDetails {
5
+ type: TIssueType;
6
+ typeKey: string;
7
+ sourceType: TSubjectType;
8
+ fileName?: string;
9
+ line?: number;
10
+ column?: number;
11
+ message: string;
12
+ advice?: string;
13
+ hint?: string;
14
+ }
15
+ export declare class YiniParseError extends Error {
16
+ readonly type: TIssueType;
17
+ readonly typeKey: string;
18
+ readonly sourceType: TSubjectType;
19
+ readonly fileName?: string;
20
+ readonly line?: number;
21
+ readonly column?: number;
22
+ readonly advice?: string;
23
+ readonly hint?: string;
24
+ constructor(details: YiniParseErrorDetails);
25
+ }
4
26
  /**
5
27
  * This class handles all error/notice reporting and processes exit/throwing.
6
28
  */
@@ -8,6 +30,7 @@ export declare class ErrorDataHandler {
8
30
  private readonly subjectType;
9
31
  private readonly fileName;
10
32
  private readonly persistThreshold;
33
+ private readonly isDiagnosticOutputEnabled;
11
34
  private readonly isQuiet;
12
35
  private readonly isSilent;
13
36
  private readonly isThrowOnError;
@@ -30,10 +53,12 @@ export declare class ErrorDataHandler {
30
53
  - Level 1 = abort on errors only
31
54
  - Level 2 = abort even on warnings
32
55
  */
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).
56
+ constructor(subjectType: TSubjectType, fileName?: string | undefined, bailSensitivityLevel?: TBailSensitivityLevel, isDiagnosticOutputEnabled?: boolean, // Opt in to writing diagnostics to stderr.
57
+ isQuiet?: boolean, // When diagnostic output is enabled, show errors only.
34
58
  isSilent?: boolean, // Suppress all output (even errors, exit code only).
35
59
  isThrowOnError?: boolean);
36
60
  private makeIssue;
61
+ private throwParseError;
37
62
  /**
38
63
  * After pushing processing may continue or exit, depending on the error
39
64
  * and/or the bail threshold (that can be optionally set by the user).
@@ -50,6 +75,9 @@ export declare class ErrorDataHandler {
50
75
  */
51
76
  pushOrBail(locInput: IErrorLocationInput | undefined, type: TIssueType, msgWhat: string, msgWhy?: string, msgHint?: string): void;
52
77
  private formatSignificantMessageLine;
78
+ private shouldEmitDiagnostics;
79
+ private shouldEmitNonErrors;
80
+ private emitDiagnosticToStderr;
53
81
  private emitFatalError;
54
82
  private emitInternalError;
55
83
  private emitSyntaxError;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ErrorDataHandler = exports.toErrorLocation = void 0;
3
+ exports.ErrorDataHandler = exports.YiniParseError = exports.toErrorLocation = void 0;
4
4
  // src/core/errorDataHandler.ts
5
5
  const env_1 = require("../config/env");
6
6
  const print_1 = require("../utils/print");
@@ -29,6 +29,66 @@ const issueTitle = [
29
29
  'Notice:',
30
30
  'Info:',
31
31
  ];
32
+ class YiniParseError extends Error {
33
+ constructor(details) {
34
+ super(formatThrownMessage(details));
35
+ Object.setPrototypeOf(this, new.target.prototype);
36
+ this.name = getErrorName(details.type);
37
+ this.type = details.type;
38
+ this.typeKey = details.typeKey;
39
+ this.sourceType = details.sourceType;
40
+ this.fileName = details.fileName;
41
+ this.line = details.line;
42
+ this.column = details.column;
43
+ this.advice = details.advice;
44
+ this.hint = details.hint;
45
+ // Node prints `error.stack` for uncaught errors. Keep parser failures
46
+ // focused on the user's document instead of internal parser frames.
47
+ this.stack = `${this.name}: ${this.message}`;
48
+ }
49
+ }
50
+ exports.YiniParseError = YiniParseError;
51
+ const getErrorName = (type) => {
52
+ switch (type) {
53
+ case 'Syntax-Error':
54
+ case 'Syntax-Warning':
55
+ return 'YiniSyntaxError';
56
+ case 'Internal-Error':
57
+ case 'Fatal-Error':
58
+ return 'YiniInternalError';
59
+ default:
60
+ return 'YiniParseError';
61
+ }
62
+ };
63
+ const formatThrownMessage = (details) => {
64
+ const lines = [];
65
+ const location = formatThrownLocation(details);
66
+ const title = details.type.replace(/-/g, ' ');
67
+ lines.push(location
68
+ ? `${title} in ${location}: ${details.message}`
69
+ : `${title}: ${details.message}`);
70
+ if (details.advice) {
71
+ lines.push(details.advice);
72
+ }
73
+ if (details.hint) {
74
+ lines.push(`Hint: ${details.hint}`);
75
+ }
76
+ return lines.join('\n');
77
+ };
78
+ const formatThrownLocation = (details) => {
79
+ if (details.sourceType === 'None/Ignore')
80
+ return '';
81
+ let location = details.sourceType === 'File'
82
+ ? details.fileName || 'YINI file'
83
+ : 'inline YINI content';
84
+ if (details.line !== undefined) {
85
+ location += `:${details.line}`;
86
+ if (details.column !== undefined) {
87
+ location += `:${details.column}`;
88
+ }
89
+ }
90
+ return location;
91
+ };
32
92
  /**
33
93
  * This class handles all error/notice reporting and processes exit/throwing.
34
94
  */
@@ -42,7 +102,8 @@ class ErrorDataHandler {
42
102
  - Level 1 = abort on errors only
43
103
  - Level 2 = abort even on warnings
44
104
  */
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).
105
+ constructor(subjectType, fileName = undefined, bailSensitivityLevel = '1-Abort-on-Errors', isDiagnosticOutputEnabled = false, // Opt in to writing diagnostics to stderr.
106
+ isQuiet = false, // When diagnostic output is enabled, show errors only.
46
107
  isSilent = false, // Suppress all output (even errors, exit code only).
47
108
  isThrowOnError = false) {
48
109
  this.errors = [];
@@ -58,6 +119,7 @@ class ErrorDataHandler {
58
119
  this.subjectType = subjectType;
59
120
  this.fileName = fileName;
60
121
  this.persistThreshold = bailSensitivityLevel;
122
+ this.isDiagnosticOutputEnabled = isDiagnosticOutputEnabled;
61
123
  this.isQuiet = isQuiet;
62
124
  this.isSilent = isSilent;
63
125
  this.isThrowOnError = isThrowOnError;
@@ -75,6 +137,19 @@ class ErrorDataHandler {
75
137
  (0, env_1.isDebug)() && console.log(issue);
76
138
  return issue;
77
139
  }
140
+ throwParseError(type, issue, msgWhatInclLineNum) {
141
+ throw new YiniParseError({
142
+ type,
143
+ typeKey: issue.typeKey,
144
+ sourceType: this.subjectType,
145
+ fileName: this.fileName,
146
+ line: issue.line,
147
+ column: issue.column,
148
+ message: msgWhatInclLineNum,
149
+ advice: issue.advice,
150
+ hint: issue.hint,
151
+ });
152
+ }
78
153
  /**
79
154
  * After pushing processing may continue or exit, depending on the error
80
155
  * and/or the bail threshold (that can be optionally set by the user).
@@ -118,13 +193,16 @@ class ErrorDataHandler {
118
193
  lineNum: lineNum || 0, // 1-based, if n/a use 0.
119
194
  colNum: colNum || 0, // 1-based, if n/a use 0.
120
195
  };
121
- if (!this.isSilent) {
122
- console.log(); // Print an empty line before outputting message.
196
+ if (this.shouldEmitDiagnostics()) {
197
+ console.error(); // Print an empty line before outputting message.
123
198
  }
124
199
  switch (type) {
125
200
  case 'Internal-Error':
126
201
  this.numInternalErrors++;
127
- this.errors.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint));
202
+ {
203
+ const issue = this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint);
204
+ this.errors.push(issue);
205
+ }
128
206
  this.emitInternalError(loc, msgWhatInclLineNum, msgWhy, msgHint);
129
207
  if (this.persistThreshold === '1-Abort-on-Errors' ||
130
208
  this.persistThreshold === '2-Abort-Even-on-Warnings') {
@@ -132,16 +210,16 @@ class ErrorDataHandler {
132
210
  (0, print_1.debugPrint)('Skipped throwing');
133
211
  }
134
212
  else {
135
- const thrownMsg = msgWhy
136
- ? `Internal-Error: ${msgWhatInclLineNum}. ${msgWhy}`
137
- : `Internal-Error: ${msgWhatInclLineNum}`;
138
- throw new Error(thrownMsg);
213
+ this.throwParseError(type, this.errors[this.errors.length - 1], msgWhatInclLineNum);
139
214
  }
140
215
  }
141
216
  break;
142
217
  case 'Syntax-Error':
143
218
  this.numSyntaxErrors++;
144
- this.errors.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint));
219
+ {
220
+ const issue = this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint);
221
+ this.errors.push(issue);
222
+ }
145
223
  this.emitSyntaxError(loc, msgWhatInclLineNum, msgWhy, msgHint);
146
224
  if (this.persistThreshold === '1-Abort-on-Errors' ||
147
225
  this.persistThreshold === '2-Abort-Even-on-Warnings') {
@@ -149,16 +227,16 @@ class ErrorDataHandler {
149
227
  (0, print_1.debugPrint)('Skipped throwing');
150
228
  }
151
229
  else {
152
- const thrownMsg = msgWhy
153
- ? `Syntax-Error: ${msgWhatInclLineNum}. ${msgWhy}`
154
- : `Syntax-Error: ${msgWhatInclLineNum}`;
155
- throw new Error(thrownMsg);
230
+ this.throwParseError(type, this.errors[this.errors.length - 1], msgWhatInclLineNum);
156
231
  }
157
232
  }
158
233
  break;
159
234
  case 'Syntax-Warning':
160
235
  this.numSyntaxWarnings++;
161
- this.warnings.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint));
236
+ {
237
+ const issue = this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint);
238
+ this.warnings.push(issue);
239
+ }
162
240
  if (!this.isQuiet) {
163
241
  this.emitSyntaxWarning(loc, msgWhatInclLineNum, msgWhy, msgHint);
164
242
  }
@@ -167,10 +245,7 @@ class ErrorDataHandler {
167
245
  (0, print_1.debugPrint)('Skipped throwing');
168
246
  }
169
247
  else {
170
- const thrownMsg = msgWhy
171
- ? `Syntax-Error: ${msgWhatInclLineNum}. ${msgWhy}`
172
- : `Syntax-Error: ${msgWhatInclLineNum}`;
173
- throw new Error(thrownMsg);
248
+ this.throwParseError(type, this.warnings[this.warnings.length - 1], msgWhatInclLineNum);
174
249
  }
175
250
  }
176
251
  break;
@@ -198,10 +273,7 @@ class ErrorDataHandler {
198
273
  // In test, throw an error instead of bailing/exiting.
199
274
  // IMPORTANT: Never exit with exit code since this is a library!
200
275
  {
201
- const thrownMsg = msgWhy
202
- ? `Internal-Error: ${msgWhatInclLineNum}. ${msgWhy}`
203
- : `Internal-Error: ${msgWhatInclLineNum}`;
204
- throw new Error(thrownMsg);
276
+ this.throwParseError('Internal-Error', this.errors[this.errors.length - 1], msgWhatInclLineNum);
205
277
  }
206
278
  }
207
279
  }
@@ -229,68 +301,53 @@ class ErrorDataHandler {
229
301
  }
230
302
  }
231
303
  }
232
- /*
233
- * - error/warning → console.error / console.warn
234
- * - notice/info → console.log / console.info
235
- */
304
+ shouldEmitDiagnostics() {
305
+ return this.isDiagnosticOutputEnabled && !this.isSilent;
306
+ }
307
+ shouldEmitNonErrors() {
308
+ return this.shouldEmitDiagnostics() && !this.isQuiet;
309
+ }
310
+ emitDiagnosticToStderr(write, messageHeader, msgWhat = '', msgWhy = '', msgHint = '') {
311
+ write(messageHeader);
312
+ msgWhat && write(msgWhat);
313
+ msgWhy && write(msgWhy);
314
+ msgHint && write(msgHint);
315
+ write();
316
+ }
236
317
  emitFatalError(loc, msgWhat = 'Something went wrong!', msgWhy = '', msgHint = '') {
237
318
  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.
319
+ if (this.shouldEmitDiagnostics()) {
320
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
244
321
  }
245
322
  }
246
323
  emitInternalError(loc, msgWhat = 'Something went wrong!', msgWhy = '', msgHint = '') {
247
324
  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.
325
+ if (this.shouldEmitDiagnostics()) {
326
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
254
327
  }
255
328
  }
256
329
  emitSyntaxError(loc, msgWhat, msgWhy = '', msgHint = '') {
257
330
  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.
331
+ if (this.shouldEmitDiagnostics()) {
332
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
264
333
  }
265
334
  }
266
335
  emitSyntaxWarning(loc, msgWhat, msgWhy = '', msgHint = '') {
267
336
  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.
337
+ if (this.shouldEmitNonErrors()) {
338
+ this.emitDiagnosticToStderr(console.warn.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
274
339
  }
275
340
  }
276
341
  emitNotice(loc, msgWhat, msgWhy = '', msgHint = '') {
277
342
  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.
343
+ if (this.shouldEmitNonErrors()) {
344
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
284
345
  }
285
346
  }
286
347
  emitInfo(loc, msgWhat, msgWhy = '', msgHint = '') {
287
348
  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.
349
+ if (this.shouldEmitNonErrors()) {
350
+ this.emitDiagnosticToStderr(console.error.bind(console), messageHeader, msgWhat, msgWhy, msgHint);
294
351
  }
295
352
  }
296
353
  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();
@@ -177,11 +196,10 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
177
196
  (0, env_1.isDebug)() && console.debug(finalJSResult);
178
197
  (0, print_1.debugPrint)();
179
198
  if (coreOptions.rules.initialMode === 'strict') {
180
- // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
181
- errorHandler.pushOrBail(undefined, 'Syntax-Warning', 'Warning: Strict initialMode is not yet fully implemented.', 'Some validation rules may still be missing or incomplete.');
182
199
  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) {
200
+ if (coreOptions.isDiagnosticOutputEnabled &&
201
+ !coreOptions.isQuiet &&
202
+ !coreOptions.isSilent) {
185
203
  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
204
  }
187
205
  }
@@ -208,7 +226,8 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
208
226
  const constructedMetadata = (0, resultMetadataBuilder_1.buildResultMetadata)(params);
209
227
  (0, print_1.debugPrint)('getNumOfErrors(): ' + errorHandler.getNumOfErrors());
210
228
  // Print a summary line at the end if any errors or warnings.
211
- if (!coreOptions.isQuiet && !coreOptions.isSilent) {
229
+ if (coreOptions.isDiagnosticOutputEnabled &&
230
+ !coreOptions.isSilent) {
212
231
  const errors = errorHandler.getNumOfErrors();
213
232
  const warnings = errorHandler.getNumOfWarnings();
214
233
  // Notes: