mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
static validating
This commit is contained in:
@@ -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;
|
||||
export default GDScriptCompletionItemProvider;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user