Skip to content

Commit d05152f

Browse files
committed
Create some post-diagnostics
1 parent de4d244 commit d05152f

File tree

1 file changed

+228
-4
lines changed

1 file changed

+228
-4
lines changed

src/diagnostic.ts

Lines changed: 228 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
import { Diagnostic, DiagnosticSeverity, DocumentDiagnosticReportKind, FullDocumentDiagnosticReport, Range } from 'lsp-types';
1+
import { CodeAction, CodeActionKind, Diagnostic, DiagnosticSeverity, DocumentDiagnosticReportKind, FullDocumentDiagnosticReport, Range } from 'lsp-types';
2+
import { ImportStatement, NodeType, ProgramStatement, RuleStatement, Statement, TokenType, isCompilerError, statementIsA } from './types.js';
3+
import { existsSync, readFileSync, statSync } from 'fs';
24
import { sysparser, syxparser } from './ast.js';
35
import { tokenizeSys, tokenizeSyx } from './lexer.js';
4-
import { isCompilerError } from './types.js';
5-
import { readFileSync } from 'fs';
6+
import { dictionary } from './dictionary/index.js';
7+
import { fileURLToPath } from 'url';
8+
import { join } from 'path';
9+
10+
11+
// Use with addRange to include semicolons
12+
const semiRange: Range = { end: { line: 0, character: 1 }, start: { line: 0, character: 0 } };
613

714
/**
815
* Creates a diagnostic report from the file path given.
@@ -19,8 +26,12 @@ export function createSyntaxScriptDiagnosticReport(filePath: string, fileContent
1926

2027
const content = fileContent ?? readFileSync(filePath).toString();
2128
const tokens = (isSyx ? tokenizeSyx : tokenizeSys)(content);
22-
(isSyx ? syxparser : sysparser).parseTokens(tokens, filePath);
29+
const ast = (isSyx ? syxparser : sysparser).parseTokens(tokens, filePath);
2330

31+
items.push(...exportableCheck(ast, filePath));
32+
items.push(...ruleConflictCheck(ast, filePath));
33+
items.push(...sameRuleCheck(ast, filePath));
34+
items.push(...importedExistentCheck(ast, filePath));
2435
} catch (error) {
2536
if (isCompilerError(error)) {
2637
items.push({
@@ -37,6 +48,215 @@ export function createSyntaxScriptDiagnosticReport(filePath: string, fileContent
3748

3849
}
3950

51+
// Checks rule conflicts and adds warnings when there is two defined rules that conflict each other
52+
function ruleConflictCheck(ast: ProgramStatement, filePath: string): Diagnostic[] {
53+
const items: Diagnostic[] = [];
54+
55+
ast.body.forEach(stmt => {
56+
if (statementIsA(stmt, NodeType.Rule)) {
57+
const dictRule = dictionary.Rules.find(r => r.name === stmt.rule);
58+
59+
ast.body.filter(r => statementIsA(r, NodeType.Rule)).filter(r => r.range !== stmt.range).map(r => r as RuleStatement).forEach(otherRules => {
60+
if (dictRule.conflicts.includes(otherRules.rule)) items.push({
61+
message: `Rule '${otherRules.rule}' conflicts with '${stmt.rule}', Both of them should not be defined.`,
62+
range: subRange(otherRules.range),
63+
source: 'syntax-script',
64+
severity: DiagnosticSeverity.Warning,
65+
data: [
66+
{
67+
title: `Remove ${stmt.rule} definition`,
68+
kind: CodeActionKind.QuickFix,
69+
edit: {
70+
changes: {
71+
[filePath]: [
72+
{
73+
range: subRange(addRange(stmt.range, semiRange)),
74+
newText: ''
75+
}
76+
]
77+
}
78+
}
79+
},
80+
{
81+
title: `Remove ${otherRules.rule} definition`,
82+
kind: CodeActionKind.QuickFix,
83+
edit: {
84+
changes: {
85+
[filePath]: [
86+
{
87+
range: subRange(addRange(otherRules.range, semiRange)),
88+
newText: ''
89+
}
90+
]
91+
}
92+
}
93+
}
94+
] as CodeAction[]
95+
});
96+
});
97+
}
98+
});
99+
100+
return items;
101+
}
102+
103+
// Checks if same rule is defined twice
104+
function sameRuleCheck(ast: ProgramStatement, filePath: string): Diagnostic[] {
105+
const items: Diagnostic[] = [];
106+
107+
ast.body.forEach(stmt => {
108+
if (statementIsA(stmt, NodeType.Rule)) {
109+
ast.body.filter(r => statementIsA(r, NodeType.Rule)).filter(r => r.range !== stmt.range).map(r => r as RuleStatement).forEach(otherRules => {
110+
if (otherRules.rule === stmt.rule) items.push({
111+
message: `Rule '${stmt.rule}' is already defined.`,
112+
range: subRange(stmt.range),
113+
source: 'syntax-script',
114+
severity: DiagnosticSeverity.Error,
115+
data: [
116+
{
117+
title: 'Remove this definition',
118+
kind: CodeActionKind.QuickFix,
119+
edit: {
120+
changes: {
121+
[filePath]: [
122+
{
123+
range: subRange(addRange(stmt.range, semiRange)),
124+
newText: ''
125+
}
126+
]
127+
}
128+
}
129+
}
130+
] as CodeAction[]
131+
});
132+
});
133+
}
134+
});
135+
136+
return items;
137+
}
138+
139+
// Checks if an import statements refers to an empty file
140+
function importedExistentCheck(ast: ProgramStatement, filePath: string): Diagnostic[] {
141+
const items: Diagnostic[] = [];
142+
143+
ast.body.filter(r => statementIsA(r, NodeType.Import)).map(r => r as ImportStatement).forEach(stmt => {
144+
145+
const filePathButPath = fileURLToPath(filePath);
146+
const fullPath = join(filePathButPath, '../', stmt.path);
147+
if (!existsSync(fullPath)) items.push({
148+
message: `Can't find file '${fullPath}' imported from '${filePathButPath}'`,
149+
severity: DiagnosticSeverity.Error,
150+
range: subRange(stmt.range),
151+
source: 'syntax-script',
152+
data: [
153+
{
154+
title: 'Remove this import statement',
155+
kind: CodeActionKind.QuickFix,
156+
edit: {
157+
changes: {
158+
[filePath]: [
159+
{ range: subRange(addRange(stmt.range, semiRange)), newText: '' }
160+
]
161+
}
162+
}
163+
}
164+
] as CodeAction[]
165+
});
166+
167+
if (existsSync(fullPath)) {
168+
const status = statSync(fullPath);
169+
170+
if (!status.isFile()) items.push({
171+
message: `'${fullPath}' imported from '${filePathButPath}' doesn't seem to be a file.`,
172+
severity: DiagnosticSeverity.Error,
173+
range: subRange(stmt.range),
174+
source: 'syntax-script',
175+
data: [
176+
{
177+
title: 'Remove this import statement',
178+
kind: CodeActionKind.QuickFix,
179+
edit: {
180+
changes: {
181+
[filePath]: [
182+
{ range: subRange(addRange(stmt.range, semiRange)), newText: '' }
183+
]
184+
}
185+
}
186+
}
187+
] as CodeAction[]
188+
});
189+
190+
if (!fullPath.endsWith('.syx')) items.push({
191+
message: `'${fullPath}' imported from '${filePathButPath}' cannot be imported.`,
192+
severity: DiagnosticSeverity.Error,
193+
range: subRange(stmt.range),
194+
source: 'syntax-script',
195+
data: [
196+
{
197+
title: 'Remove this import statement',
198+
kind: CodeActionKind.QuickFix,
199+
edit: {
200+
changes: {
201+
[filePath]: [
202+
{ range: subRange(addRange(stmt.range, semiRange)), newText: '' }
203+
]
204+
}
205+
}
206+
}
207+
] as CodeAction[]
208+
});
209+
}
210+
211+
});
212+
213+
return items;
214+
}
215+
216+
// Checks if every exported statement it actually exportable
217+
// TODO this doesnt work for some reason
218+
function exportableCheck(ast: ProgramStatement, filePath: string): Diagnostic[] {
219+
220+
const items: Diagnostic[] = [];
221+
222+
ast.body.forEach(stmt => {
223+
224+
items.push({
225+
message: `${stmt.modifiers.map(r => r.type).join(',')}l`,
226+
range: subRange(stmt.range),
227+
severity: DiagnosticSeverity.Error,
228+
source: 'syntax-script',
229+
data: []
230+
});
231+
232+
if (stmt.modifiers.some(t => t.type === TokenType.ExportKeyword) && !dictionary.ExportableNodeTypes.includes(stmt.type)) items.push({
233+
message: 'This statement cannot be exported.',
234+
range: subRange(stmt.range),
235+
severity: DiagnosticSeverity.Error,
236+
source: 'syntax-script',
237+
data: [
238+
{
239+
title: 'Remove export keyword',
240+
kind: CodeActionKind.QuickFix,
241+
edit: {
242+
changes: {
243+
[filePath]: [
244+
{
245+
newText: '', range: subRange(stmt.modifiers.find(r => r.type === TokenType.ExportKeyword).range)
246+
}
247+
]
248+
}
249+
}
250+
}
251+
] as CodeAction[]
252+
});
253+
254+
// if (dictionary.ExportableNodeTypes.includes(stmt.type)) c((stmt as GlobalStatement).body);
255+
});
256+
257+
return items;
258+
}
259+
40260
/**
41261
* Modifies the given range to be zero-based.
42262
* @param {Range} r Any range.
@@ -52,4 +272,8 @@ export function subRange(r: Range): Range {
52272
const d = r.end.line;
53273

54274
return { start: { character: a === 0 ? 0 : a - 1, line: b === 0 ? 0 : b - 1 }, end: { character: c === 0 ? 0 : c - 1, line: d === 0 ? 0 : d - 1 } };
275+
}
276+
277+
function addRange(r: Range, r2: Range): Range {
278+
return { end: { line: r.end.line + r2.end.line, character: r.end.character + r.end.character }, start: { character: r.start.character, line: r.start.line } };
55279
}

0 commit comments

Comments
 (0)