From 425e8214edc877f17daaf1111544353591d13413 Mon Sep 17 00:00:00 2001 From: Geequlim Date: Sun, 25 Dec 2016 22:10:35 +0800 Subject: [PATCH] static validating --- src/gdscript/completion.ts | 16 +------ src/gdscript/diagnostic.ts | 83 +++++++++++++++++++----------------- src/gdscript/symbolparser.ts | 24 ++++++----- src/tool_manager.ts | 8 ++-- src/window_watcher.ts | 12 +++--- 5 files changed, 69 insertions(+), 74 deletions(-) diff --git a/src/gdscript/completion.ts b/src/gdscript/completion.ts index 664dc3f..c4f4467 100644 --- a/src/gdscript/completion.ts +++ b/src/gdscript/completion.ts @@ -30,8 +30,8 @@ interface CompletionResult { } class GDScriptCompletionItemProvider implements CompletionItemProvider { - constructor() { + constructor() { } provideCompletionItems(document : TextDocument, position : Position, token : CancellationToken) : CompletionItem[] | Thenable < CompletionItem[] > | CompletionList | Thenable < CompletionList > { @@ -74,16 +74,4 @@ class GDScriptCompletionItemProvider implements CompletionItemProvider { } - -class GDScriptCompleter { - private _provider: Disposable; - constructor() { - this._provider = languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.'); - } - - dispose() { - this._provider.dispose(); - } -} - -export default GDScriptCompleter; \ No newline at end of file +export default GDScriptCompletionItemProvider; \ No newline at end of file diff --git a/src/gdscript/diagnostic.ts b/src/gdscript/diagnostic.ts index 59d1d9e..e1094fc 100644 --- a/src/gdscript/diagnostic.ts +++ b/src/gdscript/diagnostic.ts @@ -41,49 +41,52 @@ class GDScriptDiagnosticSeverity { this._subscription.dispose() } - private parseGDScript(script: GDScript, request: ParseRequest) { - // console.log("Parse GDScript ", script); - let canonicalFile = vscode.Uri.file(request.path); - this._subscription.delete(canonicalFile) - if(script.valid) { // Parse symbols - // TODO + validateScript(doc: vscode.TextDocument, script: any) { + if(doc.languageId == 'gdscript') { + if(script) { + + let diagnostics = [ + ...(this.validateExpression(doc)), + ...(this.validateUnusedSymbols(doc, script)), + ]; + // Update diagnostics + this._subscription.set(doc.uri, diagnostics); + } } - // Parse errors - let diagnostics = []; - script.errors.map( error => { - let range = new vscode.Range(error.row-1, error.column, error.row-1, error.row + 10); - diagnostics.push(new vscode.Diagnostic(range, error.message, DiagnosticSeverity.Error)); - }); - // Unused variables - const checker = (name:string, line: number) => { - const lines = request.text.split(/\r?\n/); - const pattern = `[\\s\\+\\-\\*/%\\^\\(]${name}[^a-zA-Z_\\$]`; - var matchs = request.text.match(new RegExp(pattern, 'gi')); - if(matchs.length <= 1) - diagnostics.push(new vscode.Diagnostic(new vscode.Range(line-1, lines[line-1].indexOf(name), line-1, lines[line-1].indexOf(name) + name.length), `${name} is never used.`, DiagnosticSeverity.Warning)); - }; - for (let key of Object.keys(script.members.variables)) - checker(key, script.members.variables[key]); - for (let key of Object.keys(script.members.constants)) - checker(key, script.members.constants[key]); - // Update diagnostics - this._subscription.set(canonicalFile, diagnostics); } - parseDocument(doc: vscode.TextDocument) { - if(doc.languageId == 'gdscript') { - // console.log('[GodotTools]:start parsing document ', doc); - const self = this; - const request: ParseRequest = {text: doc.getText(), path: config.normalizePath(doc.fileName)}; - requestGodot({action: "parsescript",request}).then((data: any)=>{ - const result: GDScript = data.result; - if(result && vscode.window.activeTextEditor.document == doc){ - self.parseGDScript(result, request); - } - }).catch(e=>{ - console.error(e); - }); - } + private validateUnusedSymbols(doc: vscode.TextDocument,script) { + let diagnostics = []; + const text = doc.getText(); + + const check = (name:string, range: vscode.Range) => { + const pattern = `[\\s\\+\\-\\*/%\\^\\(\\[\\{]${name}[^0-9A-Za-z_]\\s*`; + var matchs = text.match(new RegExp(pattern, 'g')); + if(matchs.length <= 1) + diagnostics.push(new vscode.Diagnostic(range, `${name} is never used.`, DiagnosticSeverity.Warning)); + }; + // Unused variables + for (let key of Object.keys(script.variables)) + check(key, script.variables[key]); + for (let key of Object.keys(script.constants)) + check(key, script.variables[key]); + return diagnostics; + } + + private validateExpression(doc: vscode.TextDocument) { + let diagnostics = []; + const text = doc.getText(); + const lines = text.split(/\r?\n/); + lines.map((line:string, i: number) =>{ + const semicolonIndex = line.indexOf(';'); + if(semicolonIndex != -1) { + diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex+1), "Statement ends with a semicolon.", DiagnosticSeverity.Warning)); + } + if(line.match(/if|elif|else|for|while|func|class/g) && line.indexOf(":") == -1) { + diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, 0, i, line.length), "':' expected at end of the line.", DiagnosticSeverity.Error)); + } + }); + return diagnostics; } } diff --git a/src/gdscript/symbolparser.ts b/src/gdscript/symbolparser.ts index 64d7baf..214b93d 100644 --- a/src/gdscript/symbolparser.ts +++ b/src/gdscript/symbolparser.ts @@ -55,30 +55,34 @@ class GDScriptSymbolParser { return sm; } - let funcsnames = getMatches(text, /func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(.*\)/gi, 1); + const determRange = (key:string, array: any): Range =>{ + return new Range(array[key], lines[array[key]].indexOf(key), array[key], lines[array[key]].indexOf(key) + key.length); + }; + + let funcsnames = getMatches(text, /func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/g, 1); const funcs = findLineRanges(funcsnames, "func\\s+$X$\\s*\\(.*\\)"); for (let key of Object.keys(funcs)) - script.functions[key] = new Range(funcs[key], 0, funcs[key],lines[funcs[key]].length); + script.functions[key] = determRange(key, funcs); - let signalnames = getMatches(text, /signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(.*\)/gi, 1); + let signalnames = getMatches(text, /signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(.*\)/g, 1); const signals = findLineRanges(signalnames, "signal\\s+$X$\\s*\\(.*\\)"); for (let key of Object.keys(signals)) - script.signals[key] = new Range(signals[key], 0, signals[key],lines[signals[key]].length); + script.signals[key] = determRange(key, signals); - let varnames = getMatches(text, /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/gi, 1); + let varnames = getMatches(text, /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/g, 1); const vars = findLineRanges(varnames, "var\\s+$X$\\s*"); for (let key of Object.keys(vars)) - script.variables[key] = new Range(vars[key], 0, vars[key],lines[vars[key]].length); + script.variables[key] = determRange(key, vars); - let constnames = getMatches(text, /const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/gi, 1); + let constnames = getMatches(text, /const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/g, 1); const consts = findLineRanges(constnames, "const\\s+$X$\\s*"); for (let key of Object.keys(consts)) - script.constants[key] = new Range(consts[key], 0, consts[key],lines[consts[key]].length); + script.constants[key] = determRange(key, consts); - let classnames = getMatches(text, /class\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*extends\s+/gi, 1); + let classnames = getMatches(text, /class\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*extends\s+/g, 1); const classes = findLineRanges(classnames, "class\\s+$X$\\s*extends\\s+"); for (let key of Object.keys(classes)) - script.classes[key] = new Range(classes[key], 0, classes[key],lines[classes[key]].length); + script.classes[key] = determRange(key, classes); return script; } diff --git a/src/tool_manager.ts b/src/tool_manager.ts index 83b4584..8c9b25e 100644 --- a/src/tool_manager.ts +++ b/src/tool_manager.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import godotRequest from './request'; import GDScriptSymbolProvider from './gdscript/symbolprovider'; import GDScriptWorkspaceSymbolProvider from './gdscript/workspace_symbol_provider'; +import GDScriptCompletionItemProvider from './gdscript/completion'; var glob = require("glob") import config from './config'; import * as path from 'path'; @@ -25,13 +26,14 @@ class ToolManager { this.validate(); } this.loadClasses(); - + // documentation symbol provider this.symbolprovider = new GDScriptSymbolProvider(); vscode.languages.registerDocumentSymbolProvider('gdscript', this.symbolprovider); - + // workspace symbol provider this.workspacesymbolprovider = new GDScriptWorkspaceSymbolProvider(); vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider); - + // code completion provider + vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'"); // Commands this._disposable = vscode.Disposable.from( vscode.commands.registerCommand('godot.updateWorkspaceSymbols', this.loadWorkspaceSymbols.bind(this)) diff --git a/src/window_watcher.ts b/src/window_watcher.ts index 481c640..0a08342 100644 --- a/src/window_watcher.ts +++ b/src/window_watcher.ts @@ -13,7 +13,6 @@ class WindowWatcher { private _disposable: Disposable; private _diagnosticSeverity: GDScriptDiagnosticSeverity; private _lastText: DocumentFlag; - private _completer: GDScriptCompleter; constructor() { let subscriptions: Disposable[] = []; @@ -23,8 +22,7 @@ class WindowWatcher { window.onDidChangeTextEditorViewColumn(this.onDidChangeTextEditorViewColumn.bind(this), this, subscriptions); this._diagnosticSeverity = new GDScriptDiagnosticSeverity(); - this._completer = new GDScriptCompleter(); - this._disposable = Disposable.from(...subscriptions, this._diagnosticSeverity, this._completer); + this._disposable = Disposable.from(...subscriptions, this._diagnosticSeverity); this._lastText = {path: "-1", version: -1}; } @@ -41,9 +39,9 @@ class WindowWatcher { // console.log("[GodotTools]:onDidChangeActiveTextEditor", event); if(window.activeTextEditor != undefined) { const doc = window.activeTextEditor.document; - this._diagnosticSeverity.parseDocument(doc); + const script = config.loadSymbolsFromFile(doc.fileName); + this._diagnosticSeverity.validateScript(doc, script); this._lastText = {path: doc.fileName, version: doc.version}; - config.loadSymbolsFromFile(doc.fileName); } } @@ -56,9 +54,9 @@ class WindowWatcher { const curText: DocumentFlag= {path: doc.fileName, version: doc.version}; // Check content changed if(this._lastText.path != curText.path || this._lastText.version != curText.version) { - this._diagnosticSeverity.parseDocument(doc); + const script = config.loadSymbolsFromFile(doc.fileName); + this._diagnosticSeverity.validateScript(doc, script); this._lastText = curText; - config.loadSymbolsFromFile(doc.fileName); } }