UNPKG

9.63 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11Object.defineProperty(exports, "__esModule", { value: true });
12exports.collectGrammarDiagnostics = void 0;
13const grammarkdown_1 = require("grammarkdown");
14const utils_1 = require("./utils");
15function collectGrammarDiagnostics(report, spec, mainSource, mainGrammar, sdos, earlyErrors) {
16 var _a;
17 return __awaiter(this, void 0, void 0, function* () {
18 // *******************
19 // Parse the grammar with Grammarkdown and collect its diagnostics, if any
20 const mainHost = new grammarkdown_1.CoreAsyncHost({
21 ignoreCase: false,
22 useBuiltinGrammars: false,
23 resolveFile: file => file,
24 readFile(file) {
25 const idx = parseInt(file);
26 if (idx.toString() !== file || idx < 0 || idx >= mainGrammar.length) {
27 throw new Error('tried to read non-existent ' + file);
28 }
29 return mainGrammar[idx].source;
30 },
31 });
32 const compilerOptions = {
33 format: grammarkdown_1.EmitFormat.ecmarkup,
34 noChecks: false,
35 noUnusedParameters: true,
36 };
37 const grammar = new grammarkdown_1.Grammar(Object.keys(mainGrammar), compilerOptions, mainHost);
38 yield grammar.parse();
39 yield grammar.check();
40 const unusedParameterErrors = new Map();
41 if (grammar.diagnostics.size > 0) {
42 // `detailedMessage: false` prevents prepending line numbers, which is good because we're going to make our own
43 grammar.diagnostics
44 .getDiagnosticInfos({ formatMessage: true, detailedMessage: false })
45 .forEach(m => {
46 const idx = +m.sourceFile.filename;
47 const error = {
48 type: 'contents',
49 ruleId: `grammarkdown:${m.code}`,
50 message: m.formattedMessage,
51 node: mainGrammar[idx].element,
52 nodeRelativeLine: m.range.start.line + 1,
53 nodeRelativeColumn: m.range.start.character + 1,
54 };
55 if (m.code === grammarkdown_1.Diagnostics.Parameter_0_is_unused.code) {
56 const param = m.node;
57 const navigator = grammar.resolver.createNavigator(param);
58 navigator.moveToAncestor(node => node.kind === grammarkdown_1.SyntaxKind.Production);
59 const nodeName = navigator.getNode().name.text;
60 const paramName = param.name.text;
61 if (!unusedParameterErrors.has(nodeName)) {
62 unusedParameterErrors.set(nodeName, new Map());
63 }
64 const paramToError = unusedParameterErrors.get(nodeName);
65 paramToError.set(paramName, error);
66 }
67 else {
68 report(error);
69 }
70 });
71 }
72 // *******************
73 // Check that SDOs and Early Errors are defined in terms of productions which actually exist
74 // Also filter out any "unused parameter" warnings for grammar productions for which the parameter is used in an early error or SDO
75 const oneOffGrammars = [];
76 const actualGrammarProductions = utils_1.getProductions(grammar);
77 const grammarsAndRules = [
78 ...sdos.map(s => ({ grammar: s.grammar, rules: [s.alg], type: 'syntax-directed operation' })),
79 ...earlyErrors.map(e => ({ grammar: e.grammar, rules: e.lists, type: 'early error' })),
80 ];
81 for (const { grammar: grammarEle, rules: rulesEles, type } of grammarsAndRules) {
82 const grammarLoc = spec.locate(grammarEle);
83 if (!grammarLoc)
84 continue;
85 const { source: importSource } = grammarLoc;
86 if (grammarLoc.endTag == null) {
87 // we'll warn for this in collect-tag-diagnostics; no need to do so here
88 continue;
89 }
90 const grammarHost = grammarkdown_1.CoreAsyncHost.forFile((importSource !== null && importSource !== void 0 ? importSource : mainSource).slice(grammarLoc.startTag.endOffset, grammarLoc.endTag.startOffset));
91 const grammar = new grammarkdown_1.Grammar([grammarHost.file], { format: grammarkdown_1.EmitFormat.ecmarkup, noChecks: true }, grammarHost);
92 yield grammar.parse();
93 oneOffGrammars.push({ grammarEle, grammar });
94 const productions = utils_1.getProductions(grammar);
95 for (const [name, { production, rhses }] of productions) {
96 const originalRhses = (_a = actualGrammarProductions.get(name)) === null || _a === void 0 ? void 0 : _a.rhses;
97 if (originalRhses === undefined) {
98 const { line, column } = utils_1.getLocationInGrammar(grammar, production.pos);
99 report({
100 type: 'contents',
101 ruleId: 'undefined-nonterminal',
102 message: `Could not find a definition for LHS in ${type}`,
103 node: grammarEle,
104 nodeRelativeLine: line,
105 nodeRelativeColumn: column,
106 });
107 continue;
108 }
109 for (const rhs of rhses) {
110 if (!originalRhses.some(o => utils_1.rhsMatches(rhs, o))) {
111 const { line, column } = utils_1.getLocationInGrammar(grammar, rhs.pos);
112 report({
113 type: 'contents',
114 ruleId: 'undefined-nonterminal',
115 message: `Could not find a production matching RHS in ${type}`,
116 node: grammarEle,
117 nodeRelativeLine: line,
118 nodeRelativeColumn: column,
119 });
120 }
121 if (rhs.kind === grammarkdown_1.SyntaxKind.RightHandSide) {
122 (function noGrammarRestrictions(s) {
123 if (s === undefined) {
124 return;
125 }
126 if (s.symbol.kind === grammarkdown_1.SyntaxKind.NoSymbolHereAssertion) {
127 const { line, column } = utils_1.getLocationInGrammar(grammar, s.symbol.pos);
128 report({
129 type: 'contents',
130 ruleId: `NLTH-in-SDO`,
131 message: `Productions referenced in ${type}s should not include "no LineTerminator here" restrictions`,
132 node: grammarEle,
133 nodeRelativeLine: line,
134 nodeRelativeColumn: column,
135 });
136 }
137 // We could also enforce that lookahead restrictions are absent, but in some cases they actually do add clarity, so we just don't enforce it either way.
138 noGrammarRestrictions(s.next);
139 })(rhs.head);
140 if (rhs.constraints !== undefined) {
141 const { line, column } = utils_1.getLocationInGrammar(grammar, rhs.constraints.pos);
142 report({
143 type: 'contents',
144 ruleId: `guard-in-SDO`,
145 message: `productions referenced in ${type}s should not be gated on grammar parameters`,
146 node: grammarEle,
147 nodeRelativeLine: line,
148 nodeRelativeColumn: column,
149 });
150 }
151 }
152 }
153 // Filter out unused parameter errors for which the parameter is actually used in an SDO or Early Error
154 if (unusedParameterErrors.has(name)) {
155 const paramToError = unusedParameterErrors.get(name);
156 for (const paramName of paramToError.keys()) {
157 // This isn't the most elegant check, but it works.
158 if (rulesEles.some(r => r.innerHTML.indexOf('[' + paramName + ']') !== -1)) {
159 paramToError.delete(paramName);
160 }
161 }
162 }
163 }
164 }
165 for (const paramToError of unusedParameterErrors.values()) {
166 for (const error of paramToError.values()) {
167 report(error);
168 }
169 }
170 return {
171 grammar,
172 oneOffGrammars,
173 };
174 });
175}
176exports.collectGrammarDiagnostics = collectGrammarDiagnostics;