From 0f7d4902fd30c56a4363b09e60fee442d4ac68e3 Mon Sep 17 00:00:00 2001 From: geequlim Date: Sat, 6 May 2017 23:25:39 +0800 Subject: [PATCH] 1. Add enum and match keywords highlight support 2. Shorthand if else support fix #17 3. Fix compains with used variables in array and dictionary fix #18 4. Fix error syntax check with keywords in strings fix #12 5. Add syntax check for end of expression --- configurations/GDScript.full.tmLanguage.json | 2 +- configurations/GDScript.tmLanguage.json | 2 +- src/gdscript/diagnostic.ts | 182 +++++++++++-------- 3 files changed, 108 insertions(+), 78 deletions(-) diff --git a/configurations/GDScript.full.tmLanguage.json b/configurations/GDScript.full.tmLanguage.json index a456721..c33caa3 100644 --- a/configurations/GDScript.full.tmLanguage.json +++ b/configurations/GDScript.full.tmLanguage.json @@ -20,7 +20,7 @@ "name": "comment.line.number-sign.gdscript" }, { - "match": "\\b(?i:elif|else|for|if|while|break|continue|pass|and|in|is|not|or|return|onready|setget|breakpoint)\\b", + "match": "\\b(?i:elif|else|for|if|while|break|continue|pass|and|in|is|not|or|return|onready|setget|enum|match|breakpoint)\\b", "name": "keyword.control.gdscript" }, { diff --git a/configurations/GDScript.tmLanguage.json b/configurations/GDScript.tmLanguage.json index 743d8c7..c85f5fd 100644 --- a/configurations/GDScript.tmLanguage.json +++ b/configurations/GDScript.tmLanguage.json @@ -20,7 +20,7 @@ "name": "comment.line.number-sign.gdscript" }, { - "match": "\\b(?i:elif|else|for|if|while|break|continue|pass|and|in|is|not|or|return|onready|setget|breakpoint)\\b", + "match": "\\b(?i:elif|else|for|if|while|break|continue|pass|and|in|is|not|or|return|onready|setget|enum|match|breakpoint)\\b", "name": "keyword.control.gdscript" }, { diff --git a/src/gdscript/diagnostic.ts b/src/gdscript/diagnostic.ts index e6c05d7..58d4163 100644 --- a/src/gdscript/diagnostic.ts +++ b/src/gdscript/diagnostic.ts @@ -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).*?\\/)) - 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*\:/)) - 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*\(.*\)/)) - 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]+/)) + 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 - 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]; - } - 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)); + if(line.match(/.*?\sif\s+\w.*?\s+else\s+\w.*/)) + return + if (line.match(/.*?\\/)) + expectEndOfLine = true; + 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+]|\{.*?\}|\[.*?\]/)){ + if(!(line.match(/".*?for.*?"/) || line.match(/'.*?for.*?'/))) + diagnostics.push(new vscode.Diagnostic(range, "Invalid for expression", DiagnosticSeverity.Error)); } - else - diagnostics.push(new vscode.Diagnostic(range, "Expected indented block after 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)); + 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); + } + else if(line.match(/\:\s*$/)) + diagnostics.push(err); + }; + if(!expectEndOfLine) + blockIndetCheck(); } - 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(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 + + } + 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 && !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;