From 46c3a57d633179e362ef229546c4504af37c9ad2 Mon Sep 17 00:00:00 2001 From: Paulo Moura Date: Wed, 8 May 2024 09:14:08 +0100 Subject: [PATCH] Add `lgtdoc` tool warnings to the "Problems" pane --- CHANGELOG.md | 1 + README.md | 2 +- src/extension.ts | 7 +- src/features/logtalkDocumentationLinter.ts | 186 +++++++++++++++++++++ src/features/logtalkTerminal.ts | 31 +++- 5 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 src/features/logtalkDocumentationLinter.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 013fb4f..07311c1 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Add "Logtalk: Scan Dead Code (workspace)" command * Add `dead_code_scanner` tool warnings to the "Problems" pane +* Add `lgtdoc` tool warnings to the "Problems" pane * Update the "Known Issues" section in the readme file ## [0.20.0] diff --git a/README.md b/README.md index 18313e9..3bea2cb 100755 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ Diagrams script for converting the `.dot` files generated by the Logtalk `diagra "logtalk.scripts.timeout": 480000 -The number of milliseconds to wait before running the scripts that convert `.xml` documentation files and `.dot` diagram files to final formats when running the `lgtdoc` and `diagrams` tools. This timeout is also used to wait for a file compilation to finish before adding any compiler errors or warnings to the "Problems" pane and for waiting to answers from the Logtalk reflection API when using code navigation features. You may need to set a value larger than the default value if you're compiling big applications. +The number of milliseconds to wait before running the scripts that convert `.xml` documentation files and `.dot` diagram files to final formats when running the `lgtdoc` and `diagrams` tools. This timeout is also used to wait for a file compilation to finish before adding any compiler and tool errors or warnings to the "Problems" pane and for waiting to answers from the Logtalk reflection API when using code navigation features. You may need to set a value larger than the default value if you're compiling big applications. ## Known Issues diff --git a/src/extension.ts b/src/extension.ts index 0401bfd..8628647 100755 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,6 +15,7 @@ import LogtalkDocumentHighlightProvider from "./features/documentHighlightProvid import LogtalkTerminal from "./features/logtalkTerminal"; import LogtalkLinter from "./features/logtalkLinter"; import LogtalkDeadCodeScanner from "./features/logtalkDeadCodeScanner"; +import LogtalkDocumentationLinter from "./features/logtalkDocumentationLinter"; import LogtalkHoverProvider from "./features/hoverProvider"; import { LogtalkDeclarationProvider } from "./features/declarationProvider"; import { LogtalkDefinitionProvider } from "./features/definitionProvider"; @@ -40,8 +41,10 @@ export function activate(context: ExtensionContext) { linter.activate(subscriptions); const deadCodeScanner = new LogtalkDeadCodeScanner(context); deadCodeScanner.activate(subscriptions); + const documentationLinter = new LogtalkDocumentationLinter(context); + documentationLinter.activate(subscriptions); - DEBUG ? console.log('Linter Loaded.') : null; + DEBUG ? console.log('Linters loaded') : null; Utils.init(context); @@ -53,7 +56,7 @@ export function activate(context: ExtensionContext) { { command: "logtalk.run.tests", callback: uri => LogtalkTerminal.runTests(uri)}, { command: "logtalk.run.doclet", callback: uri => LogtalkTerminal.runDoclet(uri)}, { command: "logtalk.scan.deadCode", callback: uri => LogtalkTerminal.scanForDeadCode(uri, deadCodeScanner)}, - { command: "logtalk.generate.documentation", callback: uri => LogtalkTerminal.genDocumentation(uri)}, + { command: "logtalk.generate.documentation", callback: uri => LogtalkTerminal.genDocumentation(uri, documentationLinter)}, { command: "logtalk.generate.diagrams", callback: uri => LogtalkTerminal.genDiagrams(uri)}, { command: "logtalk.open", callback: () => LogtalkTerminal.openLogtalk()}, { command: "logtalk.rscan.deadCode", callback: uri => LogtalkTerminal.rscanForDeadCode(uri, deadCodeScanner)}, diff --git a/src/features/logtalkDocumentationLinter.ts b/src/features/logtalkDocumentationLinter.ts new file mode 100644 index 0000000..08173eb --- /dev/null +++ b/src/features/logtalkDocumentationLinter.ts @@ -0,0 +1,186 @@ +"use strict"; + +import { + CancellationToken, + CodeActionContext, + CodeActionProvider, + Command, + Diagnostic, + DiagnosticCollection, + DiagnosticSeverity, + Disposable, + ExtensionContext, + languages, + OutputChannel, + Position, + Range, + Selection, + TextDocument, + TextEditorRevealType, + Uri, + window, + workspace, + WorkspaceEdit +} from "vscode"; +import * as path from "path"; + +import LogtalkTerminal from "./logtalkTerminal"; + +export default class LogtalkDocumentationLinter implements CodeActionProvider { + + public diagnosticCollection: DiagnosticCollection; + public diagnostics: { [docName: string]: Diagnostic[] } = {}; + public diagnosticHash = []; + private filePathIds: { [id: string]: string } = {}; + private sortedDiagIndex: { [docName: string]: number[] } = {}; + private msgRegex = /(((\*|\!)\s{5}.+\n[\*|\!]\s{7}.+\n)|((\*|\!)\s{5}.+\n))[\*|\!]\s{7}.+\n[\*|\!]\s{7}in file\s(.+)\s(below line\s(\d+))/; + private executable: string; + private documentListener: Disposable; + private openDocumentListener: Disposable; + public outputChannel: OutputChannel = null; + + constructor(private context: ExtensionContext) { + this.executable = null; + this.loadConfiguration(); + } + + provideCodeActions( + document: TextDocument, + range: Range, + context: CodeActionContext, + token: CancellationToken + ): Command[] | Thenable { + let codeActions: Command[] = []; + return codeActions; + } + private parseIssue(issue: string) { + + if(this.diagnosticHash.includes(issue)) { + return true + } else { + this.diagnosticHash.push(issue) + } + + let match = issue.match(this.msgRegex) + if (match == null) { return null; } else { console.log("match!"); } + + let severity: DiagnosticSeverity; + if(match[0][0] == '*') { + severity = DiagnosticSeverity.Warning + } else { + severity = DiagnosticSeverity.Error + } + console.log(severity); + + let fileName = path.resolve(match[6]); + console.log(fileName); + let lineFrom = 0, + lineTo = 0; + console.log(match) + + if(match[8]) { + lineFrom = parseInt(match[8])-1; + lineTo = parseInt(match[8]); + } + + let fromCol = 0; + let toCol = 240; // Default horizontal range + let fromPos = new Position(lineFrom, fromCol); + let toPos = new Position(lineTo, toCol); + let range = new Range(fromPos, toPos); + let errMsg = match[1].replace(new RegExp(/\* /,'g'), '').replace(new RegExp(/\! /,'g'), ''); + let diag = new Diagnostic(range, errMsg, severity); + + if (diag) { + if (!this.diagnostics[fileName]) { + this.diagnostics[fileName] = [diag]; + } else { + this.diagnostics[fileName].push(diag); + } + } + + } + + public lint(textDocument: TextDocument, message) { + this.parseIssue(message); + this.diagnosticCollection.delete(textDocument.uri); + + for (let doc in this.diagnostics) { + let index = this.diagnostics[doc] + .map((diag, i) => { + return [diag.range.start.line, i]; + }) + .sort((a, b) => { + return a[0] - b[0]; + }); + this.sortedDiagIndex[doc] = index.map(item => { + return item[1]; + }); + this.diagnosticCollection.set(Uri.file(doc), this.diagnostics[doc]); + } + for (let doc in this.sortedDiagIndex) { + let si = this.sortedDiagIndex[doc]; + for (let i = 0; i < si.length; i++) { + let diag = this.diagnostics[doc][si[i]]; + let severity = diag.severity === DiagnosticSeverity.Error ? "ERROR" : "Warning"; + this.outputChannel.append(message) + } + if (si.length > 0) { + this.outputChannel.show(true); + } + } + } + + private loadConfiguration(): void { + let section = workspace.getConfiguration("logtalk"); + if (section) { + this.executable = section.get("executable.path", "logtalk"); + if (this.documentListener) { + this.documentListener.dispose(); + } + if (this.openDocumentListener) { + this.openDocumentListener.dispose(); + } + } + } + + public activate(subscriptions): void { + + this.diagnosticCollection = languages.createDiagnosticCollection('Logtalk Documentation Linter'); + + workspace.onDidChangeConfiguration( + this.loadConfiguration, + this, + subscriptions + ); + + if (this.outputChannel === null) { + this.outputChannel = window.createOutputChannel("Logtalk Documentation Linter"); + this.outputChannel.clear(); + } + + this.loadConfiguration(); + + // workspace.onDidOpenTextDocument(this.doPlint, this, subscriptions); + workspace.onDidCloseTextDocument( + textDocument => { + this.diagnosticCollection.delete(textDocument.uri); + }, + null, + subscriptions + ); + } + + private outputMsg(msg: string) { + this.outputChannel.append(msg + "\n"); + this.outputChannel.show(true); + } + + public dispose(): void { + this.documentListener.dispose(); + this.openDocumentListener.dispose(); + this.diagnosticCollection.clear(); + this.diagnosticCollection.dispose(); + } + +} diff --git a/src/features/logtalkTerminal.ts b/src/features/logtalkTerminal.ts index 7ed499a..5f8d0d9 100755 --- a/src/features/logtalkTerminal.ts +++ b/src/features/logtalkTerminal.ts @@ -8,6 +8,7 @@ import * as fs from "fs"; import { spawn } from "process-promises"; import LogtalkLinter from "./logtalkLinter"; import LogtalkDeadCodeScanner from "./logtalkDeadCodeScanner"; +import LogtalkDocumentationLinter from "./logtalkDocumentationLinter"; import { isFunction } from "util"; import * as fsp from "fs/promises"; import * as timers from "timers/promises"; @@ -282,10 +283,27 @@ export default class LogtalkTerminal { LogtalkTerminal.sendString(goals); } - public static async genDocumentation(uri: Uri) { + public static async genDocumentation(uri: Uri, documentationLinter: LogtalkDocumentationLinter) { if (typeof uri === 'undefined') { uri = window.activeTextEditor.document.uri; } + let textDocument = null; + let logtalkHome: string = ''; + let logtalkUser: string = ''; + // Check for Configurations + let section = workspace.getConfiguration("logtalk"); + if (section) { + logtalkHome = jsesc(section.get("home.path", "logtalk")); + logtalkUser = jsesc(section.get("user.path", "logtalk")); + } else { + throw new Error("configuration settings error: logtalk"); + } + // Open the Text Document + await workspace.openTextDocument(uri).then((document: TextDocument) => { textDocument = document }); + // Clear the Scratch Message File + let compilerMessagesFile = `${logtalkUser}/scratch/.messages`; + await fsp.rm(`${compilerMessagesFile}`, { force: true }); + // Create the Terminal LogtalkTerminal.createLogtalkTerm(); const dir0: string = LogtalkTerminal.ensureDir(uri); const loader0 = path.join(dir0, "loader"); @@ -297,6 +315,17 @@ export default class LogtalkTerminal { const marker = path.join(dir0, ".vscode_xml_files_done"); await LogtalkTerminal.waitForFile(marker); await fsp.rm(marker, { force: true }); + if(fs.existsSync(`${compilerMessagesFile}`)) { + const lines = fs.readFileSync(`${compilerMessagesFile}`).toString().split(/\r?\n/); + let message = ''; + for (const line of lines) { + message = message + line + '\n'; + if(line == '* ' || line == '! ') { + documentationLinter.lint(textDocument, message); + message = ''; + } + } + } LogtalkTerminal.spawnScript4( xmlDir0, ["documentation", "logtalk.documentation.script", LogtalkTerminal._docExec],