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.
- package/CHANGELOG.md +41 -0
- package/LICENSE +1 -1
- package/README.md +199 -37
- package/dist/YINI.d.ts +22 -7
- package/dist/YINI.js +104 -0
- package/dist/core/astBuilder.d.ts +94 -15
- package/dist/core/astBuilder.js +396 -364
- package/dist/core/errorDataHandler.d.ts +29 -1
- package/dist/core/errorDataHandler.js +120 -63
- package/dist/core/internalTypes.d.ts +10 -1
- package/dist/core/objectBuilder.js +21 -6
- package/dist/core/options/defaultParserOptions.d.ts +3 -2
- package/dist/core/options/defaultParserOptions.js +2 -1
- package/dist/core/options/optionsFunctions.js +5 -1
- package/dist/core/pipeline/pipeline.js +31 -12
- package/dist/core/runtime.js +29 -34
- package/dist/grammar/generated/YiniLexer.d.ts +28 -35
- package/dist/grammar/generated/YiniLexer.js +323 -310
- package/dist/grammar/generated/YiniParser.d.ts +158 -80
- package/dist/grammar/generated/YiniParser.js +1141 -620
- package/dist/grammar/generated/YiniParserVisitor.d.ts +77 -14
- package/dist/grammar/generated/YiniParserVisitor.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +6 -3
- package/dist/parsers/extractHeaderParts.d.ts +12 -19
- package/dist/parsers/extractHeaderParts.js +57 -46
- package/dist/parsers/parseNumber.d.ts +24 -6
- package/dist/parsers/parseNumber.js +114 -49
- package/dist/parsers/parseSectionHeader.d.ts +11 -3
- package/dist/parsers/parseSectionHeader.js +55 -43
- package/dist/parsers/parseString.js +42 -21
- package/dist/parsers/validateShebangPlacement.d.ts +4 -0
- package/dist/parsers/validateShebangPlacement.js +115 -0
- package/dist/types/index.d.ts +19 -2
- package/dist/utils/print.d.ts +1 -0
- package/dist/utils/print.js +5 -1
- package/dist/utils/string.d.ts +1 -0
- package/dist/utils/string.js +17 -1
- package/dist/utils/system.d.ts +1 -0
- package/dist/utils/system.js +6 -1
- package/dist/utils/yiniHelpers.d.ts +44 -2
- package/dist/utils/yiniHelpers.js +134 -46
- package/examples/basic.yini +1 -0
- package/examples/compare-formats.md +1 -1
- package/examples/nested.yini +1 -1
- package/examples/parse-example.ts +1 -0
- package/package.json +11 -3
- package/dist/YINI.js.map +0 -1
- package/dist/config/env.js.map +0 -1
- package/dist/core/astBuilder.js.map +0 -1
- package/dist/core/errorDataHandler.js.map +0 -1
- package/dist/core/internalTypes.js.map +0 -1
- package/dist/core/objectBuilder.js.map +0 -1
- package/dist/core/options/defaultParserOptions.js.map +0 -1
- package/dist/core/options/failLevel.js.map +0 -1
- package/dist/core/options/optionsFunctions.js.map +0 -1
- package/dist/core/parsingRules/modeFromRulesMatcher.js.map +0 -1
- package/dist/core/parsingRules/rulesConstAndGuards.js.map +0 -1
- package/dist/core/pipeline/errorListeners.js.map +0 -1
- package/dist/core/pipeline/pipeline.js.map +0 -1
- package/dist/core/resultMetadataBuilder.js.map +0 -1
- package/dist/core/runtime.js.map +0 -1
- package/dist/dev/main.d.ts +0 -1
- package/dist/dev/main.js +0 -139
- package/dist/dev/main.js.map +0 -1
- package/dist/dev/quick-test-samples/defect-inputs.d.ts +0 -37
- package/dist/dev/quick-test-samples/defect-inputs.js +0 -106
- package/dist/dev/quick-test-samples/defect-inputs.js.map +0 -1
- package/dist/dev/quick-test-samples/valid-inputs.d.ts +0 -21
- package/dist/dev/quick-test-samples/valid-inputs.js +0 -422
- package/dist/dev/quick-test-samples/valid-inputs.js.map +0 -1
- package/dist/grammar/generated/YiniLexer.js.map +0 -1
- package/dist/grammar/generated/YiniParser.js.map +0 -1
- package/dist/grammar/generated/YiniParserVisitor.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/parsers/extractHeaderParts.js.map +0 -1
- package/dist/parsers/extractSignificantYiniLine.js.map +0 -1
- package/dist/parsers/parseBoolean.js.map +0 -1
- package/dist/parsers/parseNull.js.map +0 -1
- package/dist/parsers/parseNumber.js.map +0 -1
- package/dist/parsers/parseSectionHeader.js.map +0 -1
- package/dist/parsers/parseString.js.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/number.js.map +0 -1
- package/dist/utils/object.js.map +0 -1
- package/dist/utils/pathAndFileName.js.map +0 -1
- package/dist/utils/print.js.map +0 -1
- package/dist/utils/string.js.map +0 -1
- package/dist/utils/system.js.map +0 -1
- 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,
|
|
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',
|
|
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 (
|
|
122
|
-
console.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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 (
|
|
239
|
-
console.error(
|
|
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 (
|
|
249
|
-
console.error(
|
|
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 (
|
|
259
|
-
console.error(
|
|
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 (
|
|
269
|
-
console.warn(
|
|
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 (
|
|
279
|
-
console.
|
|
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 (
|
|
289
|
-
console.
|
|
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' | '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
//
|
|
144
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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 (
|
|
229
|
+
if (coreOptions.isDiagnosticOutputEnabled &&
|
|
230
|
+
!coreOptions.isSilent) {
|
|
212
231
|
const errors = errorHandler.getNumOfErrors();
|
|
213
232
|
const warnings = errorHandler.getNumOfWarnings();
|
|
214
233
|
// Notes:
|