@nowline/core 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diagnostics/index.d.ts +106 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +110 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/diagnostics/index.ts +160 -0
- package/src/index.ts +15 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { LangiumDocument } from 'langium';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal LSP-style diagnostic shape. Langium re-exports
|
|
4
|
+
* `vscode-languageserver-types`' `Diagnostic`; we keep a narrow local type to
|
|
5
|
+
* avoid a direct coupling and to survive cross-version type relocations.
|
|
6
|
+
*/
|
|
7
|
+
export interface LangiumLikeDiagnostic {
|
|
8
|
+
message: string;
|
|
9
|
+
severity?: number;
|
|
10
|
+
code?: string | number;
|
|
11
|
+
/**
|
|
12
|
+
* Langium stamps a machine-readable category here. Built-in lexer/parser
|
|
13
|
+
* errors carry `{ code: 'lexing-error' | 'parsing-error' }`; validator
|
|
14
|
+
* diagnostics carry the i18n stash `{ code: MessageCode, args }`.
|
|
15
|
+
*/
|
|
16
|
+
data?: unknown;
|
|
17
|
+
range?: {
|
|
18
|
+
start: {
|
|
19
|
+
line: number;
|
|
20
|
+
character: number;
|
|
21
|
+
};
|
|
22
|
+
end: {
|
|
23
|
+
line: number;
|
|
24
|
+
character: number;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Minimal Chevrotain lexer-error shape (avoids importing chevrotain types). */
|
|
29
|
+
export interface LexerErrorLike {
|
|
30
|
+
message: string;
|
|
31
|
+
line?: number;
|
|
32
|
+
column?: number;
|
|
33
|
+
offset?: number;
|
|
34
|
+
length?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Minimal Chevrotain parser-error shape (avoids importing chevrotain types). */
|
|
37
|
+
export interface ParserErrorLike {
|
|
38
|
+
message: string;
|
|
39
|
+
token?: {
|
|
40
|
+
startLine?: number;
|
|
41
|
+
startColumn?: number;
|
|
42
|
+
startOffset?: number;
|
|
43
|
+
endLine?: number;
|
|
44
|
+
endColumn?: number;
|
|
45
|
+
endOffset?: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export type RawDiagnosticOrigin = 'lexer' | 'parser' | 'validation';
|
|
49
|
+
/**
|
|
50
|
+
* A diagnostic collected from a built document, tagged by where it came from.
|
|
51
|
+
* Consumers switch on `origin` and run their own adapter to produce the shape
|
|
52
|
+
* they need.
|
|
53
|
+
*/
|
|
54
|
+
export type RawDiagnostic = {
|
|
55
|
+
origin: 'lexer';
|
|
56
|
+
error: LexerErrorLike;
|
|
57
|
+
} | {
|
|
58
|
+
origin: 'parser';
|
|
59
|
+
error: ParserErrorLike;
|
|
60
|
+
} | {
|
|
61
|
+
origin: 'validation';
|
|
62
|
+
diagnostic: LangiumLikeDiagnostic;
|
|
63
|
+
};
|
|
64
|
+
export declare const LANGIUM_LEXING_ERROR = "lexing-error";
|
|
65
|
+
export declare const LANGIUM_PARSING_ERROR = "parsing-error";
|
|
66
|
+
/**
|
|
67
|
+
* True for the lexer/parser errors Langium re-folds into `doc.diagnostics`
|
|
68
|
+
* inside `validateDocument()`. Callers that already surface those errors
|
|
69
|
+
* directly from `parseResult.lexerErrors` / `parserErrors` skip these copies
|
|
70
|
+
* so each syntax error is reported once.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isBuiltinParseDiagnostic(data: unknown): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Collect every diagnostic from a built Langium document exactly once,
|
|
75
|
+
* classified by origin. This is the single place that knows Langium's
|
|
76
|
+
* `validateDocument()` re-folds lexer + parser errors into `doc.diagnostics`:
|
|
77
|
+
* we surface those from `parseResult` and skip the re-folded copies, so no
|
|
78
|
+
* consumer can re-introduce the double-count.
|
|
79
|
+
*
|
|
80
|
+
* Order matches insertion (lexer, then parser, then validation diagnostics);
|
|
81
|
+
* consumers that display by source position sort as needed.
|
|
82
|
+
*/
|
|
83
|
+
export declare function collectDocumentDiagnostics(doc: LangiumDocument): RawDiagnostic[];
|
|
84
|
+
/** Extract a `did you mean "X"` suggestion target from a message, if present. */
|
|
85
|
+
export declare function extractSuggestion(message: string): string | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Heuristic fallback code for validator diagnostics that predate stable
|
|
88
|
+
* codes. Prefer {@link resolveDiagnosticCode}, which checks the stable code
|
|
89
|
+
* first and only falls back to this.
|
|
90
|
+
*/
|
|
91
|
+
export declare function inferCodeFromMessage(message: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* The stable validator code if present. Validator diagnostics stash
|
|
94
|
+
* `{ code: MessageCode, args }` in `data`; the `args`-array guard avoids
|
|
95
|
+
* mistaking other `data` payloads (VS Code code actions, Langium's built-in
|
|
96
|
+
* `{ code: 'lexing-error' }`, …) for a stable code.
|
|
97
|
+
*/
|
|
98
|
+
export declare function stableValidatorCode(data: unknown): string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the diagnostic code shown to users / machine consumers. Prefers the
|
|
101
|
+
* stable validator code (`NL.Exxxx`) carried in `data`, then Langium's
|
|
102
|
+
* top-level `code`, then a message-substring heuristic. Shared so the CLI,
|
|
103
|
+
* preview, and embed label the same diagnostic identically.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveDiagnosticCode(diag: LangiumLikeDiagnostic): string;
|
|
106
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diagnostics/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE;QACJ,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3C,GAAG,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KAC5C,CAAC;CACL;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE;QACJ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACL;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACnB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GAC5C;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAIlE,eAAO,MAAM,oBAAoB,iBAAiB,CAAC;AACnD,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAI/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,EAAE,CAchF;AAID,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGrE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAW5D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAMrE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,CAMzE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Shared diagnostic-collection primitives for every surface that turns a
|
|
2
|
+
// built Langium document into user-facing diagnostics (the CLI's text/JSON
|
|
3
|
+
// output, the browser preview table, the embed). The collection +
|
|
4
|
+
// classification logic lives here so the tricky Langium-folding behavior is
|
|
5
|
+
// defined exactly once; consumers keep their own thin mappers to their
|
|
6
|
+
// output shapes (CliDiagnostic, DiagnosticRow, …).
|
|
7
|
+
// Langium's `DocumentValidator` stamps these on the lexer/parser errors it
|
|
8
|
+
// folds into `doc.diagnostics` (see langium/lib/validation/document-validator).
|
|
9
|
+
export const LANGIUM_LEXING_ERROR = 'lexing-error';
|
|
10
|
+
export const LANGIUM_PARSING_ERROR = 'parsing-error';
|
|
11
|
+
/**
|
|
12
|
+
* True for the lexer/parser errors Langium re-folds into `doc.diagnostics`
|
|
13
|
+
* inside `validateDocument()`. Callers that already surface those errors
|
|
14
|
+
* directly from `parseResult.lexerErrors` / `parserErrors` skip these copies
|
|
15
|
+
* so each syntax error is reported once.
|
|
16
|
+
*/
|
|
17
|
+
export function isBuiltinParseDiagnostic(data) {
|
|
18
|
+
if (!data || typeof data !== 'object')
|
|
19
|
+
return false;
|
|
20
|
+
const code = data.code;
|
|
21
|
+
return code === LANGIUM_LEXING_ERROR || code === LANGIUM_PARSING_ERROR;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Collect every diagnostic from a built Langium document exactly once,
|
|
25
|
+
* classified by origin. This is the single place that knows Langium's
|
|
26
|
+
* `validateDocument()` re-folds lexer + parser errors into `doc.diagnostics`:
|
|
27
|
+
* we surface those from `parseResult` and skip the re-folded copies, so no
|
|
28
|
+
* consumer can re-introduce the double-count.
|
|
29
|
+
*
|
|
30
|
+
* Order matches insertion (lexer, then parser, then validation diagnostics);
|
|
31
|
+
* consumers that display by source position sort as needed.
|
|
32
|
+
*/
|
|
33
|
+
export function collectDocumentDiagnostics(doc) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const error of doc.parseResult.lexerErrors) {
|
|
36
|
+
out.push({ origin: 'lexer', error: error });
|
|
37
|
+
}
|
|
38
|
+
for (const error of doc.parseResult.parserErrors) {
|
|
39
|
+
out.push({ origin: 'parser', error: error });
|
|
40
|
+
}
|
|
41
|
+
for (const diagnostic of doc.diagnostics ?? []) {
|
|
42
|
+
const row = diagnostic;
|
|
43
|
+
if (isBuiltinParseDiagnostic(row.data))
|
|
44
|
+
continue;
|
|
45
|
+
out.push({ origin: 'validation', diagnostic: row });
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
const DID_YOU_MEAN_RE = /did you mean ['"]?([^'"?]+)['"]?\??/i;
|
|
50
|
+
/** Extract a `did you mean "X"` suggestion target from a message, if present. */
|
|
51
|
+
export function extractSuggestion(message) {
|
|
52
|
+
const match = message.match(DID_YOU_MEAN_RE);
|
|
53
|
+
return match ? match[1].trim() : undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Heuristic fallback code for validator diagnostics that predate stable
|
|
57
|
+
* codes. Prefer {@link resolveDiagnosticCode}, which checks the stable code
|
|
58
|
+
* first and only falls back to this.
|
|
59
|
+
*/
|
|
60
|
+
export function inferCodeFromMessage(message) {
|
|
61
|
+
const lower = message.toLowerCase();
|
|
62
|
+
if (lower.includes('duplicate identifier'))
|
|
63
|
+
return 'duplicate-identifier';
|
|
64
|
+
if (lower.includes('unknown reference') || lower.includes('did you mean'))
|
|
65
|
+
return 'unknown-reference';
|
|
66
|
+
if (lower.includes('circular'))
|
|
67
|
+
return 'circular-dependency';
|
|
68
|
+
if (lower.includes('requires') && lower.includes('date:'))
|
|
69
|
+
return 'missing-date';
|
|
70
|
+
if (lower.includes('duration'))
|
|
71
|
+
return 'duration';
|
|
72
|
+
if (lower.includes('include'))
|
|
73
|
+
return 'include';
|
|
74
|
+
if (lower.includes('indent'))
|
|
75
|
+
return 'indentation';
|
|
76
|
+
return 'validation';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The stable validator code if present. Validator diagnostics stash
|
|
80
|
+
* `{ code: MessageCode, args }` in `data`; the `args`-array guard avoids
|
|
81
|
+
* mistaking other `data` payloads (VS Code code actions, Langium's built-in
|
|
82
|
+
* `{ code: 'lexing-error' }`, …) for a stable code.
|
|
83
|
+
*/
|
|
84
|
+
export function stableValidatorCode(data) {
|
|
85
|
+
if (!data || typeof data !== 'object')
|
|
86
|
+
return undefined;
|
|
87
|
+
const obj = data;
|
|
88
|
+
if (typeof obj.code !== 'string')
|
|
89
|
+
return undefined;
|
|
90
|
+
if (!Array.isArray(obj.args))
|
|
91
|
+
return undefined;
|
|
92
|
+
return obj.code;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the diagnostic code shown to users / machine consumers. Prefers the
|
|
96
|
+
* stable validator code (`NL.Exxxx`) carried in `data`, then Langium's
|
|
97
|
+
* top-level `code`, then a message-substring heuristic. Shared so the CLI,
|
|
98
|
+
* preview, and embed label the same diagnostic identically.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveDiagnosticCode(diag) {
|
|
101
|
+
const stable = stableValidatorCode(diag.data);
|
|
102
|
+
if (stable)
|
|
103
|
+
return stable;
|
|
104
|
+
if (typeof diag.code === 'string' && diag.code !== '')
|
|
105
|
+
return diag.code;
|
|
106
|
+
if (typeof diag.code === 'number')
|
|
107
|
+
return String(diag.code);
|
|
108
|
+
return inferCodeFromMessage(diag.message);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/diagnostics/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { collectDocumentDiagnostics, extractSuggestion, inferCodeFromMessage, isBuiltinParseDiagnostic, LANGIUM_LEXING_ERROR, LANGIUM_PARSING_ERROR, type LangiumLikeDiagnostic, type LexerErrorLike, type ParserErrorLike, type RawDiagnostic, type RawDiagnosticOrigin, resolveDiagnosticCode, stableValidatorCode, } from './diagnostics/index.js';
|
|
1
2
|
export * from './generated/ast.js';
|
|
2
3
|
export * from './generated/module.js';
|
|
3
4
|
export { ALL_CODES, type MessageCode } from './i18n/codes.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACH,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,cAAc,EACd,EAAE,GACL,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACH,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,eAAe,GAClB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,qBAAqB,EACrB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACH,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,cAAc,EACd,EAAE,GACL,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACH,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,eAAe,GAClB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { collectDocumentDiagnostics, extractSuggestion, inferCodeFromMessage, isBuiltinParseDiagnostic, LANGIUM_LEXING_ERROR, LANGIUM_PARSING_ERROR, resolveDiagnosticCode, stableValidatorCode, } from './diagnostics/index.js';
|
|
1
2
|
export * from './generated/ast.js';
|
|
2
3
|
export * from './generated/module.js';
|
|
3
4
|
export { ALL_CODES } from './i18n/codes.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAoB,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAGH,cAAc,EACd,EAAE,GACL,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAQH,eAAe,GAClB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EAMrB,qBAAqB,EACrB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAoB,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAGH,cAAc,EACd,EAAE,GACL,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAQH,eAAe,GAClB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Shared diagnostic-collection primitives for every surface that turns a
|
|
2
|
+
// built Langium document into user-facing diagnostics (the CLI's text/JSON
|
|
3
|
+
// output, the browser preview table, the embed). The collection +
|
|
4
|
+
// classification logic lives here so the tricky Langium-folding behavior is
|
|
5
|
+
// defined exactly once; consumers keep their own thin mappers to their
|
|
6
|
+
// output shapes (CliDiagnostic, DiagnosticRow, …).
|
|
7
|
+
|
|
8
|
+
import type { LangiumDocument } from 'langium';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Minimal LSP-style diagnostic shape. Langium re-exports
|
|
12
|
+
* `vscode-languageserver-types`' `Diagnostic`; we keep a narrow local type to
|
|
13
|
+
* avoid a direct coupling and to survive cross-version type relocations.
|
|
14
|
+
*/
|
|
15
|
+
export interface LangiumLikeDiagnostic {
|
|
16
|
+
message: string;
|
|
17
|
+
severity?: number;
|
|
18
|
+
code?: string | number;
|
|
19
|
+
/**
|
|
20
|
+
* Langium stamps a machine-readable category here. Built-in lexer/parser
|
|
21
|
+
* errors carry `{ code: 'lexing-error' | 'parsing-error' }`; validator
|
|
22
|
+
* diagnostics carry the i18n stash `{ code: MessageCode, args }`.
|
|
23
|
+
*/
|
|
24
|
+
data?: unknown;
|
|
25
|
+
range?: {
|
|
26
|
+
start: { line: number; character: number };
|
|
27
|
+
end: { line: number; character: number };
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Minimal Chevrotain lexer-error shape (avoids importing chevrotain types). */
|
|
32
|
+
export interface LexerErrorLike {
|
|
33
|
+
message: string;
|
|
34
|
+
line?: number;
|
|
35
|
+
column?: number;
|
|
36
|
+
offset?: number;
|
|
37
|
+
length?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Minimal Chevrotain parser-error shape (avoids importing chevrotain types). */
|
|
41
|
+
export interface ParserErrorLike {
|
|
42
|
+
message: string;
|
|
43
|
+
token?: {
|
|
44
|
+
startLine?: number;
|
|
45
|
+
startColumn?: number;
|
|
46
|
+
startOffset?: number;
|
|
47
|
+
endLine?: number;
|
|
48
|
+
endColumn?: number;
|
|
49
|
+
endOffset?: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type RawDiagnosticOrigin = 'lexer' | 'parser' | 'validation';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A diagnostic collected from a built document, tagged by where it came from.
|
|
57
|
+
* Consumers switch on `origin` and run their own adapter to produce the shape
|
|
58
|
+
* they need.
|
|
59
|
+
*/
|
|
60
|
+
export type RawDiagnostic =
|
|
61
|
+
| { origin: 'lexer'; error: LexerErrorLike }
|
|
62
|
+
| { origin: 'parser'; error: ParserErrorLike }
|
|
63
|
+
| { origin: 'validation'; diagnostic: LangiumLikeDiagnostic };
|
|
64
|
+
|
|
65
|
+
// Langium's `DocumentValidator` stamps these on the lexer/parser errors it
|
|
66
|
+
// folds into `doc.diagnostics` (see langium/lib/validation/document-validator).
|
|
67
|
+
export const LANGIUM_LEXING_ERROR = 'lexing-error';
|
|
68
|
+
export const LANGIUM_PARSING_ERROR = 'parsing-error';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* True for the lexer/parser errors Langium re-folds into `doc.diagnostics`
|
|
72
|
+
* inside `validateDocument()`. Callers that already surface those errors
|
|
73
|
+
* directly from `parseResult.lexerErrors` / `parserErrors` skip these copies
|
|
74
|
+
* so each syntax error is reported once.
|
|
75
|
+
*/
|
|
76
|
+
export function isBuiltinParseDiagnostic(data: unknown): boolean {
|
|
77
|
+
if (!data || typeof data !== 'object') return false;
|
|
78
|
+
const code = (data as { code?: unknown }).code;
|
|
79
|
+
return code === LANGIUM_LEXING_ERROR || code === LANGIUM_PARSING_ERROR;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Collect every diagnostic from a built Langium document exactly once,
|
|
84
|
+
* classified by origin. This is the single place that knows Langium's
|
|
85
|
+
* `validateDocument()` re-folds lexer + parser errors into `doc.diagnostics`:
|
|
86
|
+
* we surface those from `parseResult` and skip the re-folded copies, so no
|
|
87
|
+
* consumer can re-introduce the double-count.
|
|
88
|
+
*
|
|
89
|
+
* Order matches insertion (lexer, then parser, then validation diagnostics);
|
|
90
|
+
* consumers that display by source position sort as needed.
|
|
91
|
+
*/
|
|
92
|
+
export function collectDocumentDiagnostics(doc: LangiumDocument): RawDiagnostic[] {
|
|
93
|
+
const out: RawDiagnostic[] = [];
|
|
94
|
+
for (const error of doc.parseResult.lexerErrors) {
|
|
95
|
+
out.push({ origin: 'lexer', error: error as LexerErrorLike });
|
|
96
|
+
}
|
|
97
|
+
for (const error of doc.parseResult.parserErrors) {
|
|
98
|
+
out.push({ origin: 'parser', error: error as unknown as ParserErrorLike });
|
|
99
|
+
}
|
|
100
|
+
for (const diagnostic of doc.diagnostics ?? []) {
|
|
101
|
+
const row = diagnostic as LangiumLikeDiagnostic;
|
|
102
|
+
if (isBuiltinParseDiagnostic(row.data)) continue;
|
|
103
|
+
out.push({ origin: 'validation', diagnostic: row });
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const DID_YOU_MEAN_RE = /did you mean ['"]?([^'"?]+)['"]?\??/i;
|
|
109
|
+
|
|
110
|
+
/** Extract a `did you mean "X"` suggestion target from a message, if present. */
|
|
111
|
+
export function extractSuggestion(message: string): string | undefined {
|
|
112
|
+
const match = message.match(DID_YOU_MEAN_RE);
|
|
113
|
+
return match ? match[1].trim() : undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Heuristic fallback code for validator diagnostics that predate stable
|
|
118
|
+
* codes. Prefer {@link resolveDiagnosticCode}, which checks the stable code
|
|
119
|
+
* first and only falls back to this.
|
|
120
|
+
*/
|
|
121
|
+
export function inferCodeFromMessage(message: string): string {
|
|
122
|
+
const lower = message.toLowerCase();
|
|
123
|
+
if (lower.includes('duplicate identifier')) return 'duplicate-identifier';
|
|
124
|
+
if (lower.includes('unknown reference') || lower.includes('did you mean'))
|
|
125
|
+
return 'unknown-reference';
|
|
126
|
+
if (lower.includes('circular')) return 'circular-dependency';
|
|
127
|
+
if (lower.includes('requires') && lower.includes('date:')) return 'missing-date';
|
|
128
|
+
if (lower.includes('duration')) return 'duration';
|
|
129
|
+
if (lower.includes('include')) return 'include';
|
|
130
|
+
if (lower.includes('indent')) return 'indentation';
|
|
131
|
+
return 'validation';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The stable validator code if present. Validator diagnostics stash
|
|
136
|
+
* `{ code: MessageCode, args }` in `data`; the `args`-array guard avoids
|
|
137
|
+
* mistaking other `data` payloads (VS Code code actions, Langium's built-in
|
|
138
|
+
* `{ code: 'lexing-error' }`, …) for a stable code.
|
|
139
|
+
*/
|
|
140
|
+
export function stableValidatorCode(data: unknown): string | undefined {
|
|
141
|
+
if (!data || typeof data !== 'object') return undefined;
|
|
142
|
+
const obj = data as { code?: unknown; args?: unknown };
|
|
143
|
+
if (typeof obj.code !== 'string') return undefined;
|
|
144
|
+
if (!Array.isArray(obj.args)) return undefined;
|
|
145
|
+
return obj.code;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the diagnostic code shown to users / machine consumers. Prefers the
|
|
150
|
+
* stable validator code (`NL.Exxxx`) carried in `data`, then Langium's
|
|
151
|
+
* top-level `code`, then a message-substring heuristic. Shared so the CLI,
|
|
152
|
+
* preview, and embed label the same diagnostic identically.
|
|
153
|
+
*/
|
|
154
|
+
export function resolveDiagnosticCode(diag: LangiumLikeDiagnostic): string {
|
|
155
|
+
const stable = stableValidatorCode(diag.data);
|
|
156
|
+
if (stable) return stable;
|
|
157
|
+
if (typeof diag.code === 'string' && diag.code !== '') return diag.code;
|
|
158
|
+
if (typeof diag.code === 'number') return String(diag.code);
|
|
159
|
+
return inferCodeFromMessage(diag.message);
|
|
160
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
collectDocumentDiagnostics,
|
|
3
|
+
extractSuggestion,
|
|
4
|
+
inferCodeFromMessage,
|
|
5
|
+
isBuiltinParseDiagnostic,
|
|
6
|
+
LANGIUM_LEXING_ERROR,
|
|
7
|
+
LANGIUM_PARSING_ERROR,
|
|
8
|
+
type LangiumLikeDiagnostic,
|
|
9
|
+
type LexerErrorLike,
|
|
10
|
+
type ParserErrorLike,
|
|
11
|
+
type RawDiagnostic,
|
|
12
|
+
type RawDiagnosticOrigin,
|
|
13
|
+
resolveDiagnosticCode,
|
|
14
|
+
stableValidatorCode,
|
|
15
|
+
} from './diagnostics/index.js';
|
|
1
16
|
export * from './generated/ast.js';
|
|
2
17
|
export * from './generated/module.js';
|
|
3
18
|
export { ALL_CODES, type MessageCode } from './i18n/codes.js';
|