|
@@ -4,50 +4,45 @@ import {DiagnosticCollection, DiagnosticSeverity} from 'vscode';
|
|
|
import config from '../config';
|
|
|
|
|
|
interface GDParseError {
|
|
|
- message: string,
|
|
|
- column: number,
|
|
|
- row: number
|
|
|
+ message : string,
|
|
|
+ column : number,
|
|
|
+ row : number
|
|
|
}
|
|
|
|
|
|
interface GDScript {
|
|
|
- members: {
|
|
|
+ members : {
|
|
|
constants: {},
|
|
|
functions: {},
|
|
|
variables: {},
|
|
|
signals: {}
|
|
|
},
|
|
|
- base: string,
|
|
|
- errors: GDParseError[],
|
|
|
- valid: boolean,
|
|
|
- is_tool: boolean,
|
|
|
- native: string
|
|
|
+ base : string,
|
|
|
+ errors : GDParseError[],
|
|
|
+ valid : boolean,
|
|
|
+ is_tool : boolean,
|
|
|
+ native : string
|
|
|
}
|
|
|
|
|
|
interface ParseRequest {
|
|
|
- text: string,
|
|
|
- path: string
|
|
|
+ text : string,
|
|
|
+ path : string
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
class GDScriptDiagnosticSeverity {
|
|
|
- private _subscription: DiagnosticCollection;
|
|
|
-
|
|
|
+ private _subscription : DiagnosticCollection;
|
|
|
+
|
|
|
constructor() {
|
|
|
this._subscription = vscode.languages.createDiagnosticCollection("gdscript")
|
|
|
}
|
|
|
|
|
|
- dispose() {
|
|
|
+ dispose() {
|
|
|
this._subscription.dispose()
|
|
|
}
|
|
|
|
|
|
- async validateScript(doc: vscode.TextDocument, script: any) {
|
|
|
- if(doc.languageId == 'gdscript') {
|
|
|
- if(script) {
|
|
|
- let diagnostics = [
|
|
|
- ...(this.validateExpression(doc)),
|
|
|
- ...(this.validateUnusedSymbols(doc, script)),
|
|
|
- ];
|
|
|
+ async validateScript(doc : vscode.TextDocument, script : any) {
|
|
|
+ if (doc.languageId == 'gdscript') {
|
|
|
+ if (script) {
|
|
|
+ let diagnostics = [ ...(this.validateExpression(doc)), ...(this.validateUnusedSymbols(doc, script)) ];
|
|
|
this._subscription.set(doc.uri, diagnostics);
|
|
|
return true;
|
|
|
}
|
|
@@ -55,91 +50,126 @@ class GDScriptDiagnosticSeverity {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- private validateUnusedSymbols(doc: vscode.TextDocument,script) {
|
|
|
+ private validateUnusedSymbols(doc : vscode.TextDocument, script) {
|
|
|
let diagnostics = [];
|
|
|
const text = doc.getText();
|
|
|
-
|
|
|
- const check = (name:string, range: vscode.Range) => {
|
|
|
- var matchs = text.match(new RegExp(`[^\\w]\\s*${name}[^\\w]\\s*`, 'g'));
|
|
|
- let count = matchs?matchs.length:0;
|
|
|
- var incomment = text.match(new RegExp(`#.*?[^\\w]*${name}[^\\w]`, 'g'));
|
|
|
- count -= incomment?incomment.length:0;
|
|
|
- if(count <= 1)
|
|
|
+
|
|
|
+ const check = (name : string, range : vscode.Range) => {
|
|
|
+ var matchs = text.match(new RegExp(`([^\\w]|\\[|\\{)\\s*${name}\\s*([^\\w]|\\[|\\{)`, 'g'));
|
|
|
+ let count = matchs ? matchs.length : 0;
|
|
|
+ var incomment = text.match(new RegExp(`#.*?([^\\w]|\\[|\\{)\\s*${name}\\s*([^\\w]|\\[|\\{)`, 'g'));
|
|
|
+ count -= incomment ? incomment.length : 0;
|
|
|
+ if (count <= 1)
|
|
|
diagnostics.push(new vscode.Diagnostic(range, `${name} is never used.`, DiagnosticSeverity.Warning));
|
|
|
};
|
|
|
// Unused variables
|
|
|
- for (let key of Object.keys(script.variables))
|
|
|
+ for (let key of Object.keys(script.variables))
|
|
|
check(key, script.variables[key]);
|
|
|
- for (let key of Object.keys(script.constants))
|
|
|
+ for (let key of Object.keys(script.constants))
|
|
|
check(key, script.constants[key]);
|
|
|
return diagnostics;
|
|
|
}
|
|
|
|
|
|
- private validateExpression(doc: vscode.TextDocument) {
|
|
|
+ private validateExpression(doc : vscode.TextDocument) {
|
|
|
let diagnostics = [];
|
|
|
let expectEndOfLine = false;
|
|
|
const text = doc.getText();
|
|
|
const lines = text.split(/\r?\n/);
|
|
|
- lines.map((line:string, i: number) =>{
|
|
|
+ lines.map((line : string, i : number) => {
|
|
|
let matchstart = /[^\s]+.*/.exec(line);
|
|
|
let curLineStartAt = 0;
|
|
|
- if(matchstart)
|
|
|
+ if (matchstart)
|
|
|
curLineStartAt = matchstart.index;
|
|
|
+
|
|
|
// ignore comments
|
|
|
- if(line.match(/^\s*#.*/) || line.match(/^#.*/)) return
|
|
|
+ if (line.match(/^\s*#.*/) || line.match(/^#.*/))
|
|
|
+ return
|
|
|
// normalize line content
|
|
|
line = "\t" + line + "\t";
|
|
|
var range = new vscode.Range(i, curLineStartAt, i, line.length);
|
|
|
|
|
|
- if(line.match(/[^#].*?\;/) && !line.match(/[#].*?\;/)) {
|
|
|
+ if (line.match(/[^#].*?\;/) && !line.match(/[#].*?\;/)) {
|
|
|
const semicolonIndex = line.indexOf(';');
|
|
|
- diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex+1), "Statement contains a semicolon.", DiagnosticSeverity.Warning));
|
|
|
- } if (line.match(/[^#].*?/) && expectEndOfLine){
|
|
|
- if(!line.match(/.*?(\\|\:)/)){
|
|
|
+ diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex + 1), "Statement contains a semicolon.", DiagnosticSeverity.Warning));
|
|
|
+ }
|
|
|
+ if (line.match(/[^#].*?/) && expectEndOfLine) {
|
|
|
+ if (!line.match(/.*?(\\|\:)/)) {
|
|
|
diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
|
|
|
expectEndOfLine = false;
|
|
|
}
|
|
|
- if(line.match(/.*?\:/))
|
|
|
+ if (line.match(/.*?\:/))
|
|
|
expectEndOfLine = false;
|
|
|
}
|
|
|
- if(line.match(/[^\w](if|elif|else|for|while|func|class)[^\w].*?/) && !line.match(/#.*?[^\w](if|elif|else|for|while|func|class)[^\w].*?/)) {
|
|
|
- if(line.match(/(if|elif|else|for|while|func|class).*?\\/))
|
|
|
+ const colonKeywords = /\b(if|elif|else|for|while|func|class|match)\b/;
|
|
|
+ let keywords = line.match(colonKeywords)
|
|
|
+ if (keywords) {
|
|
|
+ if(line.match(new RegExp(`".*?\\s${keywords[1]}\\s.*?"`)) || line.match(new RegExp(`'.*?\\s${keywords[1]}\\s.*?\'`)))
|
|
|
+ return
|
|
|
+ if(line.match(/.*?\sif\s+\w.*?\s+else\s+\w.*/))
|
|
|
+ return
|
|
|
+ if (line.match(/.*?\\/))
|
|
|
expectEndOfLine = true;
|
|
|
- if(line.match(/(if|elif|else|for|while|func|class).*?/) && !line.match(/.*?(\\|\:)/))
|
|
|
- diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
|
|
|
- else if(line.match(/(if|elif|while|func|class)\s*\:/))
|
|
|
+ else if (line.match(/.*?\:[\s+]+[^#\s]+/))
|
|
|
+ return
|
|
|
+ else if (!line.match(/.*?(\\|\:)/))
|
|
|
+ diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
|
|
|
+ else if (line.match(/(if|elif|while|func|class|match)\s*\:/))
|
|
|
diagnostics.push(new vscode.Diagnostic(range, "Indentifier expected before ':'", DiagnosticSeverity.Error));
|
|
|
- else if(line.match(/[^\w]for[^\w]/) && !line.match(/\s+for\s\w+\s+in\s+[\w+]|\{.*?\}|\[.*?\]/))
|
|
|
- diagnostics.push(new vscode.Diagnostic(range, "Invalid for expression", DiagnosticSeverity.Error));
|
|
|
- else if(line.match(/(if|elif|while)\s*\(.*\)/))
|
|
|
+ else if (line.match(/[^\w]for[^\w]/) && !line.match(/\s+for\s\w+\s+in\s+[\w+]|\{.*?\}|\[.*?\]/)){
|
|
|
+ if(!(line.match(/".*?for.*?"/) || line.match(/'.*?for.*?'/)))
|
|
|
+ diagnostics.push(new vscode.Diagnostic(range, "Invalid for expression", DiagnosticSeverity.Error));
|
|
|
+ }
|
|
|
+ else if (line.match(/(if|elif|while|match)\s*\(.*\)/))
|
|
|
diagnostics.push(new vscode.Diagnostic(range, "Extra brackets in condition expression.", DiagnosticSeverity.Warning));
|
|
|
- if(line.match(/([^\w]if|elif|else|for|while|func|class[^\w]).*\:[ \t]+[^#\s]+/))
|
|
|
- return
|
|
|
- else if( i < lines.length-1) {
|
|
|
- let next = i+1;
|
|
|
- let nextline = lines[next];
|
|
|
- // changes nextline until finds a line containg text or comes to the last line
|
|
|
- while ((!nextline || nextline.match(/\s+$/)) && next < lines.length-1) {
|
|
|
- ++next;
|
|
|
- nextline = lines[next];
|
|
|
+ const blockIndetCheck = function() {
|
|
|
+ const err = new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error);
|
|
|
+ if (i < lines.length - 1) {
|
|
|
+ let next = i + 1;
|
|
|
+ let nextline = lines[next];
|
|
|
+ // changes nextline until finds a line containg text or comes to the last line
|
|
|
+ while (((!nextline || nextline.match(/\s+$/)) || nextline.match(/\s*#/)) && next < lines.length - 1) {
|
|
|
+ ++next;
|
|
|
+ nextline = lines[next];
|
|
|
+ }
|
|
|
+ let nextLineStartAt = -1;
|
|
|
+ let match = /[^\s]+.*/.exec(nextline);
|
|
|
+ if (match)
|
|
|
+ nextLineStartAt = match.index;
|
|
|
+
|
|
|
+ if (nextLineStartAt <= curLineStartAt)
|
|
|
+ diagnostics.push(err);
|
|
|
}
|
|
|
- let nextLineStartAt = -1;
|
|
|
- let match = /[^\s]+.*/.exec(nextline);
|
|
|
- if(match)
|
|
|
- nextLineStartAt = match.index;
|
|
|
-
|
|
|
- if(nextLineStartAt <= curLineStartAt)
|
|
|
- diagnostics.push(new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error));
|
|
|
+ else if(line.match(/\:\s*$/))
|
|
|
+ diagnostics.push(err);
|
|
|
+ };
|
|
|
+ if(!expectEndOfLine)
|
|
|
+ blockIndetCheck();
|
|
|
+ }
|
|
|
+ if(!line.match(colonKeywords) && line.match(/\:\s*$/)) {
|
|
|
+ let showErr = true;
|
|
|
+ if( i >= 1 ) {
|
|
|
+ let previous = i - 1;
|
|
|
+ let previousline = lines[previous];
|
|
|
+ while(previousline.match(/\\\s*$/) && previous>=1) {
|
|
|
+ --previous;
|
|
|
+ const ppreviousline = lines[previous];
|
|
|
+ if(ppreviousline.match(/\\\s*$/))
|
|
|
+ previousline = ppreviousline;
|
|
|
+ }
|
|
|
+ const keywords = previousline.match(colonKeywords);
|
|
|
+ if(keywords && !(previousline.match(new RegExp(`".*?\\s${keywords[1]}\\s.*?"`)) || previousline.match(new RegExp(`'.*?\\s${keywords[1]}\\s.*?'`)) ))
|
|
|
+ showErr = false
|
|
|
+
|
|
|
}
|
|
|
- else
|
|
|
- diagnostics.push(new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error));
|
|
|
+ if(showErr)
|
|
|
+ diagnostics.push(new vscode.Diagnostic(range, "Expected end of statement after expression", DiagnosticSeverity.Error));
|
|
|
}
|
|
|
- if(line.match(/(if|elif|while|return)\s+\w+\s*=\s*\w+/))
|
|
|
- diagnostics.push(new vscode.Diagnostic(range, "Assignment in condition or return expressions", DiagnosticSeverity.Warning));
|
|
|
- else if (line.indexOf("==") > 0 ) {
|
|
|
+ if (line.match(/(if|elif|while|return)\s+\w+\s*=\s*\w+/))
|
|
|
+ diagnostics.push(new vscode.Diagnostic(range, "Assignment in condition or return expressions", DiagnosticSeverity.Warning));
|
|
|
+ else if (line.indexOf("==") > 0 && !line.match(/\:\s*/)) {
|
|
|
const endAt = line.indexOf("==");
|
|
|
const precontent = line.substring(0, endAt);
|
|
|
- if(!precontent.match(/\s(if|elif|while|return)\s/) && !precontent.match(/=[^=]/)) {
|
|
|
+ if (!precontent.match(/\s(if|elif|while|return)\s/) && !precontent.match(/=[^=]/) && !expectEndOfLine) {
|
|
|
diagnostics.push(new vscode.Diagnostic(range, "Unhandled comparation expression contains", DiagnosticSeverity.Warning));
|
|
|
}
|
|
|
}
|
|
@@ -150,7 +180,7 @@ class GDScriptDiagnosticSeverity {
|
|
|
});
|
|
|
return diagnostics;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
|
|
|
export default GDScriptDiagnosticSeverity;
|