From 7a6747d137a7cf7c90852468ded630ed1a473ea7 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Sat, 30 Mar 2024 10:12:07 -0300 Subject: [PATCH 01/13] First step: fix #147 (CI will fail) --- server/src/functionalities/autocomplete/autocomplete.ts | 4 ++-- server/src/functionalities/references.ts | 9 +-------- server/src/functionalities/symbols.ts | 3 +-- server/src/linter.ts | 5 +---- server/src/settings.ts | 3 ++- server/src/utils/vm/wollok.ts | 9 --------- 6 files changed, 7 insertions(+), 26 deletions(-) delete mode 100644 server/src/utils/vm/wollok.ts diff --git a/server/src/functionalities/autocomplete/autocomplete.ts b/server/src/functionalities/autocomplete/autocomplete.ts index 3045b258..9aac56dd 100644 --- a/server/src/functionalities/autocomplete/autocomplete.ts +++ b/server/src/functionalities/autocomplete/autocomplete.ts @@ -2,7 +2,7 @@ import { CompletionItem, CompletionItemKind, CompletionParams, InsertTextFormat, import { Class, Entity, Field, Method, Mixin, Module, Name, Node, OBJECT_MODULE, Parameter, Reference, Singleton, Environment, Import, parentModule, getAllUninitializedAttributes } from 'wollok-ts' import { TimeMeasurer } from '../../time-measurer' import { cursorNode, relativeFilePath, packageToURI } from '../../utils/text-documents' -import { isImportedIn } from '../../utils/vm/wollok' +import { isNotImportedIn } from 'wollok-ts' import { completionsForNode } from './node-completion' import { completeMessages } from './send-completion' import { match, when } from 'wollok-ts/dist/extensions' @@ -46,7 +46,7 @@ export const withImport = (mapper: CompletionItemMapper) => ( if( importedPackage && originalPackage && - isImportedIn(importedPackage, originalPackage) + isNotImportedIn(importedPackage, originalPackage) ) { result.detail = `Add import ${importedPackage.fileName ? relativeFilePath(packageToURI(importedPackage)) : importedPackage.name}${result.detail ? ` - ${result.detail}` : ''}` result.additionalTextEdits = (result.additionalTextEdits ?? []).concat( diff --git a/server/src/functionalities/references.ts b/server/src/functionalities/references.ts index b968dda8..8080ef6e 100644 --- a/server/src/functionalities/references.ts +++ b/server/src/functionalities/references.ts @@ -1,7 +1,6 @@ import { Location, ReferenceParams } from 'vscode-languageserver' -import { Environment, Method, Node, Reference, Send, Singleton } from 'wollok-ts' +import { Environment, Method, mayExecute, targettingAt } from 'wollok-ts' import { cursorNode, nodeToLocation } from '../utils/text-documents' -import { targettingAt } from 'wollok-ts' export const references = (environment: Environment) => (params: ReferenceParams): Location[] | null => { const node = cursorNode(environment, params.position, params.textDocument) @@ -12,9 +11,3 @@ export const references = (environment: Environment) => (params: ReferenceParams targettingAt(node) ).map(nodeToLocation) } - -const mayExecute = (method: Method) => (aNode: Node) => - aNode.is(Send) && - aNode.message === method.name && - // exclude cases where a message is sent to a different singleton - !(aNode.receiver.is(Reference) && aNode.receiver.target?.is(Singleton) && aNode.receiver.target !== method.parent) \ No newline at end of file diff --git a/server/src/functionalities/symbols.ts b/server/src/functionalities/symbols.ts index bcd05bc3..42c8bc61 100644 --- a/server/src/functionalities/symbols.ts +++ b/server/src/functionalities/symbols.ts @@ -1,8 +1,7 @@ import { DocumentSymbol, DocumentSymbolParams, SymbolKind, WorkspaceSymbol, WorkspaceSymbolParams } from 'vscode-languageserver' -import { Environment, Field, Method, Module, Node, Package, Program, Test, Variable } from 'wollok-ts' +import { Environment, Field, Method, Module, Node, Package, projectPackages, Program, Test, Variable } from 'wollok-ts' import { logger } from '../utils/logger' import { packageFromURI, toVSCRange, uriFromRelativeFilePath } from '../utils/text-documents' -import { projectPackages } from '../utils/vm/wollok' type Symbolyzable = Program | Test | Module | Variable | Field | Method | Test diff --git a/server/src/linter.ts b/server/src/linter.ts index 0e03c343..b0949db6 100644 --- a/server/src/linter.ts +++ b/server/src/linter.ts @@ -9,10 +9,7 @@ import { List } from 'wollok-ts/dist/extensions' import { reportValidationMessage } from './functionalities/reporter' import { updateDocumentSettings } from './settings' import { TimeMeasurer } from './time-measurer' -import { - isNodeURI, relativeFilePath, - trimIn, -} from './utils/text-documents' +import { isNodeURI, relativeFilePath, trimIn } from './utils/text-documents' // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ // INTERNAL FUNCTIONS diff --git a/server/src/settings.ts b/server/src/settings.ts index dac5f7e6..e52bdc90 100644 --- a/server/src/settings.ts +++ b/server/src/settings.ts @@ -1,4 +1,5 @@ import { Connection } from 'vscode-languageserver/node' +import { wollokLSPExtensionCode } from './shared-definitions' export interface WollokLSPSettings { maxNumberOfProblems: number @@ -46,7 +47,7 @@ export const updateDocumentSettings = async ( ): Promise => { globalSettings = ((await connection.workspace.getConfiguration({ - section: 'wollokLSP', + section: wollokLSPExtensionCode, })) as WollokLSPSettings) || defaultSettings } diff --git a/server/src/utils/vm/wollok.ts b/server/src/utils/vm/wollok.ts deleted file mode 100644 index 1d99224e..00000000 --- a/server/src/utils/vm/wollok.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Environment, Package } from 'wollok-ts' - -export const projectPackages = (environment: Environment): Package[] => - environment.members.slice(1) - -export const isImportedIn = (importedPackage: Package, importingPackage: Package): boolean => - importedPackage !== importingPackage && - !importingPackage.imports.some(imported => imported.entity.target === importedPackage) && - !importedPackage.isGlobalPackage \ No newline at end of file From cd37dc94ccc6059be0ab4a32f8beb6f7db697883 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Sat, 30 Mar 2024 19:58:29 -0300 Subject: [PATCH 02/13] Fix #146 --- client/src/test/helper.ts | 8 +- client/src/test/runTest.ts | 2 +- .../autocomplete/node-completion.ts | 4 +- server/src/functionalities/definition.ts | 7 +- server/src/functionalities/formatter.ts | 10 +- server/src/functionalities/hover.ts | 4 +- server/src/linter.ts | 14 +- server/src/server.ts | 154 +++++++++++------- server/src/utils/vm/environment.ts | 9 - 9 files changed, 124 insertions(+), 88 deletions(-) diff --git a/client/src/test/helper.ts b/client/src/test/helper.ts index 17ff4677..0a6bfd61 100644 --- a/client/src/test/helper.ts +++ b/client/src/test/helper.ts @@ -30,9 +30,9 @@ export async function activate(docUri: Uri, timeToWait = 2000): Promise { document = await workspace.openTextDocument(docUri) editor = await window.showTextDocument(document) await sleep(timeToWait) // Wait for server activation - } catch (e) { - console.error(e) - throw e + } catch (error) { + console.error(error) + throw error } } @@ -43,9 +43,11 @@ async function sleep(milliseconds: number) { export const getDocumentPath = (docPath: string): string => { return path.resolve(__dirname, path.join('..', '..', 'testFixture'), docPath) } + export const getDocumentURI = (docPath: string): Uri => { return Uri.file(getDocumentPath(docPath)) } + export const getFolderURI = (): Uri => { return Uri.file(getDocumentPath('')) } diff --git a/client/src/test/runTest.ts b/client/src/test/runTest.ts index 02539da1..d675eca7 100644 --- a/client/src/test/runTest.ts +++ b/client/src/test/runTest.ts @@ -27,7 +27,7 @@ async function main() { ], }) } catch (err) { - console.error('Failed to run tests', err) + console.error('✘ Failed to run tests', err) process.exit(1) } } diff --git a/server/src/functionalities/autocomplete/node-completion.ts b/server/src/functionalities/autocomplete/node-completion.ts index bc91c305..59d34823 100644 --- a/server/src/functionalities/autocomplete/node-completion.ts +++ b/server/src/functionalities/autocomplete/node-completion.ts @@ -2,6 +2,7 @@ import { CompletionItem } from 'vscode-languageserver' import { Node, Body, Method, Singleton, Module, Environment, Package, Class, Mixin, Describe, Program, Test, Reference, New, Import, Entity, implicitImport, is, parentImport, match, when } from 'wollok-ts' import { classCompletionItem, fieldCompletionItem, initializerCompletionItem, parameterCompletionItem, singletonCompletionItem, entityCompletionItem, withImport } from './autocomplete' import { optionModules, optionImports, optionDescribes, optionTests, optionReferences, optionMethods, optionPrograms, optionAsserts, optionConstReferences, optionInitialize, optionPropertiesAndReferences } from './options-autocomplete' +import { logger } from '../../utils/logger' export const completionsForNode = (node: Node): CompletionItem[] => { try { @@ -19,7 +20,8 @@ export const completionsForNode = (node: Node): CompletionItem[] => { when(Reference)(completeReference), when(New)(completeNew) ) - } catch { + } catch (error) { + logger.error(`✘ Completions for node failed: ${error}`, error) return completeForParent(node) } } diff --git a/server/src/functionalities/definition.ts b/server/src/functionalities/definition.ts index 7610a6cd..981e02ca 100644 --- a/server/src/functionalities/definition.ts +++ b/server/src/functionalities/definition.ts @@ -1,6 +1,7 @@ import { Location, TextDocumentPositionParams } from 'vscode-languageserver' import { Environment, Method, Module, New, Node, Reference, Self, Send, Singleton, Super, is, match, when } from 'wollok-ts' import { getNodesByPosition, nodeToLocation } from '../utils/text-documents' +import { logger } from '../utils/logger' export const definition = (environment: Environment) => ( textDocumentPosition: TextDocumentPositionParams @@ -19,7 +20,8 @@ export const getNodeDefinition = (environment: Environment) => (node: Node): Nod when(Super)(node => definedOrEmpty(superMethodDefinition(node))), when(Self)(node => definedOrEmpty(node.ancestors.find(is(Module)))) ) - } catch { + } catch (error) { + logger.error(`✘ Error in getNodeDefinition: ${error}`, error) return [node] } } @@ -43,7 +45,8 @@ const sendDefinitions = (environment: Environment) => (send: Send): Method[] => (module) => definedOrEmpty(module.lookupMethod(send.message, send.args.length)) )), ) - } catch { + } catch (error) { + logger.error(`✘ Send definitions error: ${error}`, error) return allMethodDefinitions(environment, send) } } diff --git a/server/src/functionalities/formatter.ts b/server/src/functionalities/formatter.ts index 8cad7cc2..0b6fb709 100644 --- a/server/src/functionalities/formatter.ts +++ b/server/src/functionalities/formatter.ts @@ -23,12 +23,12 @@ export const formatDocument = (environment: Environment, { formatter: formatterC }) ), ] - } catch(err) { - let message = `Could not format file '${file.fileName}'` - if(err instanceof PrintingMalformedNodeError){ - message += `: ${err.message} {${err.node.toString()}}` + } catch(error) { + let message = `✘ Could not format file '${file.fileName}'` + if (error instanceof PrintingMalformedNodeError) { + message += `: ${error.message} {${error.node.toString()}}` } - logger.error(message) + logger.error(message, error) return null } } diff --git a/server/src/functionalities/hover.ts b/server/src/functionalities/hover.ts index 413863db..2045486c 100644 --- a/server/src/functionalities/hover.ts +++ b/server/src/functionalities/hover.ts @@ -30,8 +30,8 @@ export const typeDescriptionOnHover = (environment: Environment, { typeSystem }: ], range: node.sourceMap ? toVSCRange(node.sourceMap) : undefined, } - } catch (e) { - logger.error('Failed to get type description', e) + } catch (error) { + logger.error(`✘ Failed to get type description: ${error}`, error) return null } } \ No newline at end of file diff --git a/server/src/linter.ts b/server/src/linter.ts index b0949db6..01a54141 100644 --- a/server/src/linter.ts +++ b/server/src/linter.ts @@ -4,12 +4,12 @@ import { DiagnosticSeverity, } from 'vscode-languageserver' import { TextDocument } from 'vscode-languageserver-textdocument' -import { Environment, Problem, validate } from 'wollok-ts' -import { List } from 'wollok-ts/dist/extensions' +import { Environment, List, Problem, validate } from 'wollok-ts' import { reportValidationMessage } from './functionalities/reporter' import { updateDocumentSettings } from './settings' import { TimeMeasurer } from './time-measurer' import { isNodeURI, relativeFilePath, trimIn } from './utils/text-documents' +import { logger } from './utils/logger' // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ // INTERNAL FUNCTIONS @@ -60,15 +60,17 @@ export const validateTextDocument = async (environment: Environment): Promise => { await updateDocumentSettings(connection) + const timeMeasurer = new TimeMeasurer() try { - const documentUri = relativeFilePath(textDocument.uri) - const timeMeasurer = new TimeMeasurer() const problems = validate(environment) sendDiagnostics(connection, problems, allDocuments) + } catch (error) { + logger.error(`✘ Validate text document error: ${error}`, error) + generateErrorForFile(connection, textDocument) + } finally { + const documentUri = relativeFilePath(textDocument.uri) timeMeasurer.addTime(`Validating ${documentUri}`) timeMeasurer.finalReport() - } catch (e) { - generateErrorForFile(connection, textDocument) } } diff --git a/server/src/server.ts b/server/src/server.ts index 17e0d819..d0facea7 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -59,34 +59,6 @@ const requestContext = combineLatest([environmentProvider.$environment.pipe(filt const requestProgressReporter = new ProgressReporter(connection, { identifier: 'wollok-request', title: 'Processing Request...' }) -function syncHandler(requestHandler: ServerRequestHandler): ServerRequestHandler { - return (params, cancel, workDoneProgress, resultProgress) => { - requestProgressReporter.begin() - try { - return requestHandler(params, cancel, workDoneProgress, resultProgress) - } catch (e) { - logger.error('✘ Failed to process request', e) - return null - } finally { - requestProgressReporter.end() - } - - } -} - -function waitForFirstHandler(requestHandler: (environment: Environment, settings: ClientConfigurations) => ServerRequestHandler): ServerRequestHandler { - return (params, cancel, workDoneProgress, resultProgress) => { - requestProgressReporter.begin() - return new Promise(resolve => { - firstValueFrom(requestContext).then(([newEnvironment, newSettings]) => { - const result = syncHandler(requestHandler(newEnvironment!, newSettings))(params, cancel, workDoneProgress, resultProgress) - requestProgressReporter.end() - resolve(result) - }) - }) - } -} - let hasWorkspaceFolderCapability = false connection.onInitialize((params: InitializeParams) => { @@ -122,29 +94,41 @@ connection.onInitialize((params: InitializeParams) => { }) connection.onInitialized(() => { - connection.client.register(DidChangeConfigurationNotification.type, null) + try { + connection.client.register(DidChangeConfigurationNotification.type, null) - if (hasWorkspaceFolderCapability) { - connection.workspace.onDidChangeWorkspaceFolders((_event) => { - connection.console.log('Workspace folder change event received.') - }) + if (hasWorkspaceFolderCapability) { + connection.workspace.onDidChangeWorkspaceFolders((_event) => { + connection.console.log('Workspace folder change event received.') + }) + } + initializeSettings(connection) + environmentProvider.resetEnvironment() + } catch (error) { + handleError('onInitialized failed', error) } - initializeSettings(connection) - environmentProvider.resetEnvironment() }) // Cache the settings of all open documents const documentSettings: Map> = new Map() connection.onDidChangeConfiguration(() => { - connection.workspace.getConfiguration('wollokLSP').then(settings => { - config.next(settings as ClientConfigurations) - }) + try { + connection.workspace.getConfiguration('wollokLSP').then(settings => { + config.next(settings as ClientConfigurations) + }) + } catch (error) { + handleError('onDidChangeConfiguration failed', error) + } }) // Only keep settings for open documents documents.onDidClose((change) => { - documentSettings.delete(change.document.uri) + try { + documentSettings.delete(change.document.uri) + } catch (error) { + handleError('onDidClose event failed', error) + } }) const deferredChanges: TextDocumentChangeEvent[] = [] @@ -161,8 +145,7 @@ const rebuildTextDocument = (change: TextDocumentChangeEvent) => { environmentProvider.$environment.getValue()! ) } catch (e) { - connection.console.error(`✘ Failed to rebuild document: ${e}`) - logger.error(`✘ Failed to rebuild document`, e) + handleError('Failed to rebuild document', e) } } @@ -172,24 +155,32 @@ documents.onDidChangeContent(rebuildTextDocument) // Custom requests from client connection.onRequest((change) => { - if (change.startsWith('WORKSPACE_URI')) { // WORKSPACE_URI:[uri] - setWorkspaceUri('file:' + change.split(':').pop()) - deferredChanges.forEach(rebuildTextDocument) - deferredChanges.length = 0 - } + try { + if (change.startsWith('WORKSPACE_URI')) { // WORKSPACE_URI:[uri] + setWorkspaceUri('file:' + change.split(':').pop()) + deferredChanges.forEach(rebuildTextDocument) + deferredChanges.length = 0 + } - if (change === 'STRONG_FILES_CHANGED') { // A file was deleted, renamed, moved, etc. - environmentProvider.resetEnvironment() - environmentProvider.updateEnvironmentWith(...documents.all()) + if (change === 'STRONG_FILES_CHANGED') { // A file was deleted, renamed, moved, etc. + environmentProvider.resetEnvironment() + environmentProvider.updateEnvironmentWith(...documents.all()) + } + } catch (error) { + handleError('onRequest change failed', error) } }) config.subscribe(() => { - // Revalidate all open text documents - environmentProvider.updateEnvironmentWith(...documents.all()) - documents.all().forEach(doc => - validateTextDocument(connection, documents.all())(doc)(environmentProvider.$environment.getValue()!) - ) + try { + // Revalidate all open text documents + environmentProvider.updateEnvironmentWith(...documents.all()) + documents.all().forEach(doc => + validateTextDocument(connection, documents.all())(doc)(environmentProvider.$environment.getValue()!) + ) + } catch (error) { + handleError('Updating environment failed', error) + } }) const handlers: readonly [ @@ -209,13 +200,21 @@ const handlers: readonly [ [connection.onReferences, references], ] -for (const [handlerRegistration, requestHandler] of handlers) { - handlerRegistration(waitForFirstHandler(requestHandler)) +try { + for (const [handlerRegistration, requestHandler] of handlers) { + handlerRegistration(waitForFirstHandler(requestHandler)) + } +} catch (error) { + handleError('Handling registration for first time failed', error) } requestContext.subscribe(([newEnvironment, newSettings]) => { - for (const [handlerRegistration, requestHandler] of handlers) { - handlerRegistration(syncHandler(requestHandler(newEnvironment!, newSettings))) + try { + for (const [handlerRegistration, requestHandler] of handlers) { + handlerRegistration(syncHandler(requestHandler(newEnvironment!, newSettings))) + } + } catch (error) { + handleError('There was an error while processing a request during a change of environment', error) } }) @@ -224,4 +223,41 @@ requestContext.subscribe(([newEnvironment, newSettings]) => { documents.listen(connection) // Listen on the connection -connection.listen() \ No newline at end of file +connection.listen() + +/*************************************************************************************************************/ +/* Internal functions */ +/*************************************************************************************************************/ + +function handleError(message: string, e: unknown): void { + connection.console.error(`✘ ${message}: ${e}`) + logger.error(`✘ ${message}`, e) +} + +function syncHandler(requestHandler: ServerRequestHandler): ServerRequestHandler { + return (params, cancel, workDoneProgress, resultProgress) => { + requestProgressReporter.begin() + try { + return requestHandler(params, cancel, workDoneProgress, resultProgress) + } finally { + requestProgressReporter.end() + } + } +} + +function waitForFirstHandler(requestHandler: (environment: Environment, settings: ClientConfigurations) => ServerRequestHandler): ServerRequestHandler { + return (params, cancel, workDoneProgress, resultProgress) => { + requestProgressReporter.begin() + return new Promise(resolve => { + firstValueFrom(requestContext).then(([newEnvironment, newSettings]) => { + const result = syncHandler(requestHandler(newEnvironment!, newSettings))(params, cancel, workDoneProgress, resultProgress) + requestProgressReporter.end() + resolve(result) + }, + error => { + requestProgressReporter.end() + throw error + }) + }) + } +} diff --git a/server/src/utils/vm/environment.ts b/server/src/utils/vm/environment.ts index 6de5786a..89ce629d 100644 --- a/server/src/utils/vm/environment.ts +++ b/server/src/utils/vm/environment.ts @@ -4,7 +4,6 @@ import { TextDocument } from 'vscode-languageserver-textdocument' import { Environment, buildEnvironment, inferTypes } from 'wollok-ts' import { ProgressReporter } from '../progress-reporter' import { TimeMeasurer } from '../../time-measurer' -import { logger } from '../logger' import { generateErrorForFile } from '../../linter' import { documentToFile } from '../text-documents' @@ -38,17 +37,9 @@ export class EnvironmentProvider { } return environment } catch (error) { - - // todo: remove this catch and move the logs to server.ts - const message = `✘ Failed to build environment: ${error}` documents.forEach(document => { generateErrorForFile(this.connection, document) }) - logger.error({ - level: 'error', - files: files.map(file => file.name), - message, - }) throw error } finally { this.buildProgressReporter.end() From 7c46839646b92a5a9693c2d423e03e06cdecdbaa Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Sun, 31 Mar 2024 17:51:57 -0300 Subject: [PATCH 03/13] Add info of doc --- server/src/functionalities/symbols.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/functionalities/symbols.ts b/server/src/functionalities/symbols.ts index 42c8bc61..37b09f12 100644 --- a/server/src/functionalities/symbols.ts +++ b/server/src/functionalities/symbols.ts @@ -8,7 +8,7 @@ type Symbolyzable = Program | Test | Module | Variable | Field | Method | Test export const documentSymbols = (environment: Environment) => (params: DocumentSymbolParams): DocumentSymbol[] => { const document = packageFromURI(params.textDocument.uri, environment) if (!document){ - logger.error('Could not produce symbols: document not found') + logger.error(`✘ Could not produce symbols: document ${params.textDocument.uri} not found`) return [] } return documentSymbolsFor(document) From bb6136475363860913cd8999dad3da603d8e4c81 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Sun, 31 Mar 2024 17:52:13 -0300 Subject: [PATCH 04/13] linter fixes --- server/src/functionalities/rename.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/functionalities/rename.ts b/server/src/functionalities/rename.ts index c5fee06d..6cd25f2a 100644 --- a/server/src/functionalities/rename.ts +++ b/server/src/functionalities/rename.ts @@ -17,8 +17,8 @@ export const rename = (documents: TextDocuments) => (environment: export const requestIsRenamable = (environment: Environment) => (params: RenameParams): any => { const renamedNode = cursorNode(environment, params.position, params.textDocument) - if(!renamedNode) return null - if( renamedNode.is(Reference) && renamedNode.target && isRenamable(renamedNode.target) || isRenamable(renamedNode)) { + if (!renamedNode) return null + if (renamedNode.is(Reference) && renamedNode.target && isRenamable(renamedNode.target) || isRenamable(renamedNode)) { // ToDo: switch back to defaultBehavior when https://github.com/microsoft/vscode/issues/198423 is released return { range: toVSCRange(renamedNode.sourceMap!), @@ -38,7 +38,7 @@ function renameNode(node: Renamable, newName: string, environment: Environment, const hits: (Renamable | Reference)[] = [node] const referencesRenamedNode = targettingAt(node) environment.forEach(aNode => { - if(!aNode.isSynthetic && referencesRenamedNode(aNode)) { + if (!aNode.isSynthetic && referencesRenamedNode(aNode)) { hits.push(aNode as Reference) } }) From f04bbc1a9d73459fce04dc5e842cb54c28b92c22 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Sun, 31 Mar 2024 18:12:23 -0300 Subject: [PATCH 05/13] Fix #149 - delete/rename leaves zombies problems --- client/src/extension.ts | 6 ++++-- server/src/server.ts | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index 66032861..1bc060b3 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -81,8 +81,10 @@ export function activate(context: ExtensionContext): void { }) // Force environment to restart - const revalidateWorskpace = (_event) => - client.sendRequest('STRONG_FILES_CHANGED').then(validateWorkspace) + const revalidateWorskpace = (_event) => { + const pathForChange = (file) => file.oldUri?.fsPath ?? file.fsPath + return client.sendRequest(`STRONG_FILES_CHANGED:${_event.files.map(pathForChange).join(',')}`).then(validateWorkspace) + } workspace.onDidDeleteFiles(revalidateWorskpace) workspace.onDidRenameFiles(revalidateWorskpace) diff --git a/server/src/server.ts b/server/src/server.ts index d0facea7..4412cd8f 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -155,6 +155,7 @@ documents.onDidChangeContent(rebuildTextDocument) // Custom requests from client connection.onRequest((change) => { + logger.info(`onRequest - ${change}`) try { if (change.startsWith('WORKSPACE_URI')) { // WORKSPACE_URI:[uri] setWorkspaceUri('file:' + change.split(':').pop()) @@ -162,9 +163,21 @@ connection.onRequest((change) => { deferredChanges.length = 0 } - if (change === 'STRONG_FILES_CHANGED') { // A file was deleted, renamed, moved, etc. + if (change.startsWith('STRONG_FILES_CHANGED')) { // A file was deleted, renamed, moved, etc. environmentProvider.resetEnvironment() environmentProvider.updateEnvironmentWith(...documents.all()) + + // Remove zombies problems + const files = change.split(':').pop() + if (files) { + const uris = files.split(',') + setTimeout(() => { + uris.forEach(uri => { + logger.info(`Removing diagnostics from ${uri}`) + connection.sendDiagnostics({ uri, diagnostics: [] }) + }) + }, 100) + } } } catch (error) { handleError('onRequest change failed', error) From 8dd72f1183751800475165b387ef5ad24c79f1a3 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Sun, 31 Mar 2024 23:53:38 -0300 Subject: [PATCH 06/13] Fix #121 - ms to open dynamic diagram --- client/src/commands.ts | 3 ++- server/src/settings.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/commands.ts b/client/src/commands.ts index 9c7ad8d6..48af997d 100644 --- a/client/src/commands.ts +++ b/client/src/commands.ts @@ -79,6 +79,7 @@ export const startRepl = (): Task => { const replTask = wollokCLITask('repl', `Wollok Repl: ${getCurrentFileName(currentDocument)}`, cliCommands) const openDynamicDiagram = wollokLSPConfiguration.get('openDynamicDiagramOnRepl') as boolean + const millisecondsToOpenDynamicDiagram = wollokLSPConfiguration.get('millisecondsToOpenDynamicDiagram') as number if (openDynamicDiagram) { setTimeout(() => { const openInternalDynamicDiagram = wollokLSPConfiguration.get('openInternalDynamicDiagram') as boolean @@ -87,7 +88,7 @@ export const startRepl = (): Task => { } else { vscode.env.openExternal(vscode.Uri.parse(DYNAMIC_DIAGRAM_URI)) } - }, 1000) + }, millisecondsToOpenDynamicDiagram) } return replTask } diff --git a/server/src/settings.ts b/server/src/settings.ts index e52bdc90..c157fc61 100644 --- a/server/src/settings.ts +++ b/server/src/settings.ts @@ -8,6 +8,7 @@ export interface WollokLSPSettings { openInternalDynamicDiagram: boolean, dynamicDiagramDarkMode: boolean, maxThreshold: number, + millisecondsToOpenDynamicDiagram: number, } // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ @@ -30,6 +31,7 @@ const defaultSettings: WollokLSPSettings = { openInternalDynamicDiagram: true, dynamicDiagramDarkMode: true, maxThreshold: 100, + millisecondsToOpenDynamicDiagram: 1000, } let globalSettings: WollokLSPSettings = defaultSettings From df4b6a5f5ff203c0027384a60bbb8e9df2b7e8f0 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Mon, 1 Apr 2024 00:34:55 -0300 Subject: [PATCH 07/13] Fix #122 - skip diagram in ts-cli --- client/src/commands.ts | 11 ++++++----- package.json | 38 ++++++++++++++++++++++++-------------- server/src/settings.ts | 2 +- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/client/src/commands.ts b/client/src/commands.ts index 48af997d..fead7252 100644 --- a/client/src/commands.ts +++ b/client/src/commands.ts @@ -72,17 +72,18 @@ const DYNAMIC_DIAGRAM_URI = 'http://localhost:3000/' export const startRepl = (): Task => { const currentDocument = window.activeTextEditor?.document const wollokLSPConfiguration = workspace.getConfiguration(wollokLSPExtensionCode) - const dynamicDiagramDarkMode = wollokLSPConfiguration.get('dynamicDiagramDarkMode') ?? false - const cliCommands = [`repl`, ...getFiles(currentDocument), '--skipValidations', dynamicDiagramDarkMode ? '--darkMode' : ''] + const dynamicDiagramDarkMode = wollokLSPConfiguration.get('dynamicDiagram.dynamicDiagramDarkMode') as boolean + const openDynamicDiagram = wollokLSPConfiguration.get('dynamicDiagram.openDynamicDiagramOnRepl') as boolean + const millisecondsToOpenDynamicDiagram = wollokLSPConfiguration.get('dynamicDiagram.millisecondsToOpenDynamicDiagram') as number + + const cliCommands = [`repl`, ...getFiles(currentDocument), '--skipValidations', dynamicDiagramDarkMode ? '--darkMode' : '', openDynamicDiagram ? '': '--skipDiagram'] // Terminate previous tasks vscode.commands.executeCommand('workbench.action.terminal.killAll') const replTask = wollokCLITask('repl', `Wollok Repl: ${getCurrentFileName(currentDocument)}`, cliCommands) - const openDynamicDiagram = wollokLSPConfiguration.get('openDynamicDiagramOnRepl') as boolean - const millisecondsToOpenDynamicDiagram = wollokLSPConfiguration.get('millisecondsToOpenDynamicDiagram') as number if (openDynamicDiagram) { setTimeout(() => { - const openInternalDynamicDiagram = wollokLSPConfiguration.get('openInternalDynamicDiagram') as boolean + const openInternalDynamicDiagram = wollokLSPConfiguration.get('dynamicDiagram.openInternalDynamicDiagram') as boolean if (openInternalDynamicDiagram) { vscode.commands.executeCommand('simpleBrowser.show', DYNAMIC_DIAGRAM_URI) } else { diff --git a/package.json b/package.json index bf438a85..9665adcc 100644 --- a/package.json +++ b/package.json @@ -66,19 +66,21 @@ "scope": "resource", "type": "boolean", "description": "Abbreviate assignments", - "default": true + "default": true, + "order": 0 }, "wollokLSP.formatter.maxWidth": { "scope": "resource", "type": "number", "description": "Maximum width allowed in a line", - "default": 80 + "default": 80, + "order": 1 }, "wollokLSP.cli-path": { "scope": "resource", "type": "string", "description": "Path to Wollok-CLI.", - "order": 0 + "order": 10 }, "wollokLSP.language": { "scope": "resource", @@ -90,14 +92,14 @@ ], "default": "Based on Local Environment", "description": "Language used while reporting linter errors and warnings.", - "order": 1 + "order": 11 }, "wollokLSP.maxNumberOfProblems": { "scope": "resource", "type": "number", "default": 100, "description": "Controls the maximum number of problems produced by the server.", - "order": 2 + "order": 12 }, "wollokLSP.trace.server": { "scope": "window", @@ -109,41 +111,49 @@ ], "default": "off", "description": "Traces the communication between VS Code and the language server.", - "order": 3 + "order": 20 }, - "wollokLSP.openDynamicDiagramOnRepl": { + "wollokLSP.dynamicDiagram.openDynamicDiagramOnRepl": { "scope": "resource", "type": "boolean", "default": true, "description": "Opens the dynamic diagram when running the REPL.", - "order": 4 + "order": 30 }, - "wollokLSP.openInternalDynamicDiagram": { + "wollokLSP.dynamicDiagram.openInternalDynamicDiagram": { "scope": "resource", "type": "boolean", "default": true, "description": "If true, opens an internal dynamic diagram inside Wollok IDE. If false, it will open a new external browser.", - "order": 5 + "order": 31 + }, + "wollokLSP.dynamicDiagram.millisecondsToOpenDynamicDiagram": { + "scope": "resource", + "type": "number", + "default": 1000, + "description": "Milliseconds we wait until we open Dynamic Diagram in Browser.", + "order": 32 }, - "wollokLSP.dynamicDiagramDarkMode": { + "wollokLSP.dynamicDiagram.dynamicDiagramDarkMode": { "scope": "resource", "type": "boolean", "default": true, "description": "If true, opens dynamic diagram in Dark Mode. Otherwise, it uses Light Mode.", - "order": 6 + "order": 33 }, "wollokLSP.maxThreshold": { "scope": "resource", "type": "number", "default": 100, "description": "Maximum threshold in milliseconds: if an operation takes longer, it will be saved in the log file.", - "order": 7 + "order": 40 }, "wollokLSP.typeSystem.enabled": { "scope": "resource", "type": "boolean", "description": "Enable Type System (experimental)", - "default": false + "default": false, + "order": 50 } } }, diff --git a/server/src/settings.ts b/server/src/settings.ts index c157fc61..4fa8ccba 100644 --- a/server/src/settings.ts +++ b/server/src/settings.ts @@ -62,4 +62,4 @@ export const initializeSettings = async ( export const lang = (): string => languageDescription[globalSettings.language] || envLang() -export const maxThreshold = (): number => globalSettings.maxThreshold \ No newline at end of file +export const maxThreshold = (): number => globalSettings.maxThreshold From 3010480e7ef244c74993efede73a6877aadbb015 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Mon, 1 Apr 2024 19:43:20 -0300 Subject: [PATCH 08/13] Fix #111 - wollok icon --- images/wlk.png | Bin 0 -> 1856 bytes package.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 images/wlk.png diff --git a/images/wlk.png b/images/wlk.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae677fc417ccd5fe436824eb5e5bec4dde26ec7 GIT binary patch literal 1856 zcmV-G2fz49f9HcucTB(g1MODbAh@f5&Jpx^LE82fZf;em0C=z{Kd2DS3eme%{(GX?Aj-HTb zAuxO~LI3fL+la!7r>?16I@sxL5R_qG?`M;;GJYLTHh*>1vIm`?>@g@Ji3hBnvxHkOQ26T+&xq4_#csyo4KT~{o~yU^hN-P$V>5>5+IYNX3d;h zUwzKFlj!*AZ$3@rvE-c0nfdjebEtBndRs+cVDO`;JOOBa?-zf1ZEx}SzCOANbR2J; zYc8lLQ;fUHZfY~hlNKkEJycM?K-?*5!~P-v{hsbWRPX-%bDuX|x!iT6Wxh#C&U1oi zpX~}62XKJ81fF<&u(NCDp!asw{rB5_k8NH(EdhYX2Rgf7>;JfGiMU)zDcRZI`uxo0 z)GzIxB9oMq7BL~62B4#;Z0i;GwDf?l_jaO~74Q7r3;tW(O?@qwOYgtm-jGOk(ax7% zczEm6Gm)+u+c85ORZWLAZ#`&3a>4W$TXT!9Ch!1&E;_ZxCny)oH)f9~TX*`ezHnIz z3KBgbK+p~Y>JQJkfLwnQlccwswsaevwWd9rEtdT+pqs45!;a5>O~s@{R4lG zo})4XLJ?>YfDWtlfnp{*2*bYn-DN&+Cr}elbD;l0d&`{VrGcTqo!T!hUQZya!w(+b z{7>lo$uom^*a9mv3nFU?je?FL~99(Hr=I5?k+UQD4eeBwg zUiK&QLeiAf;u+trX^a`t!Oq$UAlI!@1l^>_+u_L<@DEYiak0~+r&$T&eD-XCr_w>B zvg1UjD}72L+2%t9^_AjIF%E=NuuN6j%pChF#rC!Rg7%NhfA=J=WaOV1C&{Xufq_<+ zdlK(vl%5<%w%QpOYz+={5cI>1_JOud-gjbV!E@0Z$6Yl0$Ni_9{~f2)6liu$b{7gT zxyah?JJoVHQCW&;UQ`)wKWMFw9Y)3WwZ944DiBN&b-1~5Lr&;qI?8<8QIY3e?&M5x z5Ougz|7^~fUQqb#aMKxgcE<HQytIS4^2u_Rys0000 Date: Mon, 1 Apr 2024 19:43:31 -0300 Subject: [PATCH 09/13] Fix e2e test --- client/src/test/commands.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/test/commands.test.ts b/client/src/test/commands.test.ts index 40f6b802..3b585b47 100644 --- a/client/src/test/commands.test.ts +++ b/client/src/test/commands.test.ts @@ -99,7 +99,7 @@ suite('Should run commands', () => { startRepl, ` repl ${toPosix( pepitaURI.fsPath, - )} --skipValidations --darkMode -p ${expectedPathByShell( + )} --skipValidations --darkMode -p ${expectedPathByShell( 'bash', folderURI.fsPath, )}`, From 43c3d757dd81bf3c5984846469e11eb4c848d1f4 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Tue, 2 Apr 2024 09:42:50 -0300 Subject: [PATCH 10/13] Back to old image but better definition --- images/wlk.png | Bin 1856 -> 0 bytes images/wollokFile.png | Bin 0 -> 55535 bytes package.json | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 images/wlk.png create mode 100644 images/wollokFile.png diff --git a/images/wlk.png b/images/wlk.png deleted file mode 100644 index 4ae677fc417ccd5fe436824eb5e5bec4dde26ec7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1856 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 Date: Sun, 28 Apr 2024 19:16:09 -0300 Subject: [PATCH 11/13] Removing migrated functions --- server/src/functionalities/definition.ts | 65 ++---------------------- 1 file changed, 4 insertions(+), 61 deletions(-) diff --git a/server/src/functionalities/definition.ts b/server/src/functionalities/definition.ts index 981e02ca..53b2958f 100644 --- a/server/src/functionalities/definition.ts +++ b/server/src/functionalities/definition.ts @@ -7,73 +7,16 @@ export const definition = (environment: Environment) => ( textDocumentPosition: TextDocumentPositionParams ): Location[] => { const cursorNodes = getNodesByPosition(environment, textDocumentPosition) - const definitions = getNodeDefinition(environment)(cursorNodes.reverse()[0]) + const definitions = getDefinition(environment)(cursorNodes.reverse()[0]) return definitions.map(nodeToLocation) } // WOLLOK-TS: hablar con Nahue/Ivo, para mí desde acá para abajo todo se podria migrar a wollok-ts -export const getNodeDefinition = (environment: Environment) => (node: Node): Node[] => { +export const getDefinition = (environment: Environment) => (node: Node): Node[] => { try { - return match(node)( - when(Reference)(node => definedOrEmpty(referenceDefinition(node))), - when(Send)(sendDefinitions(environment)), - when(Super)(node => definedOrEmpty(superMethodDefinition(node))), - when(Self)(node => definedOrEmpty(node.ancestors.find(is(Module)))) - ) + getNodeDefinition(environment)(node) } catch (error) { - logger.error(`✘ Error in getNodeDefinition: ${error}`, error) + logger.error(`✘ Error in getDefinition: ${error}`, error) return [node] } } - -function referenceDefinition(ref: Reference): Node | undefined { - return ref.target -} - - -const sendDefinitions = (environment: Environment) => (send: Send): Method[] => { - try { - return match(send.receiver)( - when(Reference)(node => { - const target = node.target - return target && is(Singleton)(target) ? - definedOrEmpty(target.lookupMethod(send.message, send.args.length)) - : allMethodDefinitions(environment, send) - }), - when(New)(node => definedOrEmpty(node.instantiated.target?.lookupMethod(send.message, send.args.length))), - when(Self)(_ => moduleFinderWithBackup(environment, send)( - (module) => definedOrEmpty(module.lookupMethod(send.message, send.args.length)) - )), - ) - } catch (error) { - logger.error(`✘ Send definitions error: ${error}`, error) - return allMethodDefinitions(environment, send) - } -} - -function superMethodDefinition(superNode: Super): Method | undefined { - const currentMethod = superNode.ancestors.find(is(Method))! - const module = superNode.ancestors.find(is(Module)) - return module ? module.lookupMethod(currentMethod.name, superNode.args.length, { lookupStartFQN: module.fullyQualifiedName }) : undefined -} - -function allMethodDefinitions(environment: Environment, send: Send): Method[] { - const arity = send.args.length - const name = send.message - return environment.descendants.filter(n => - is(Method)(n) && - n.name === name && - n.parameters.length === arity - ) as Method[] -} - - -// UTILS -const moduleFinderWithBackup = (environment: Environment, send: Send) => (methodFinder: (module: Module) => Method[]) => { - const module = send.ancestors.find(is(Module)) - return module ? methodFinder(module) : allMethodDefinitions(environment, send) -} - -function definedOrEmpty(value: T | undefined): T[] { - return value ? [value] : [] -} \ No newline at end of file From d164c8cfc48d75977522e158fb00c4a52091fb28 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Fri, 3 May 2024 08:02:04 -0300 Subject: [PATCH 12/13] Add new version for wollok-ts --- package.json | 2 +- server/src/functionalities/definition.ts | 29 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index cefc04ed..5b1228a4 100644 --- a/package.json +++ b/package.json @@ -203,7 +203,7 @@ "lint-staged": "lint-staged" }, "dependencies": { - "wollok-ts": "4.1.0" + "wollok-ts": "4.1.1" }, "devDependencies": { "@types/expect": "^24.3.0", diff --git a/server/src/functionalities/definition.ts b/server/src/functionalities/definition.ts index 53b2958f..7e0ed3bd 100644 --- a/server/src/functionalities/definition.ts +++ b/server/src/functionalities/definition.ts @@ -1,5 +1,5 @@ import { Location, TextDocumentPositionParams } from 'vscode-languageserver' -import { Environment, Method, Module, New, Node, Reference, Self, Send, Singleton, Super, is, match, when } from 'wollok-ts' +import { Environment, Method, Module, Node, Reference, Self, Send, Super, is, match, sendDefinitions, when } from 'wollok-ts' import { getNodesByPosition, nodeToLocation } from '../utils/text-documents' import { logger } from '../utils/logger' @@ -11,12 +11,35 @@ export const definition = (environment: Environment) => ( return definitions.map(nodeToLocation) } -// WOLLOK-TS: hablar con Nahue/Ivo, para mí desde acá para abajo todo se podria migrar a wollok-ts export const getDefinition = (environment: Environment) => (node: Node): Node[] => { try { - getNodeDefinition(environment)(node) + return getNodeDefinition(environment)(node) } catch (error) { logger.error(`✘ Error in getDefinition: ${error}`, error) return [node] } } + +// TODO: terminar de migrar a wollok-ts estas 4 definiciones +export const getNodeDefinition = (environment: Environment) => (node: Node): Node[] => { + try { + return match(node)( + when(Reference)(node => definedOrEmpty(node.target)), + when(Send)(sendDefinitions(environment)), + when(Super)(node => definedOrEmpty(superMethodDefinition(node))), + when(Self)(node => definedOrEmpty(getParentModule(node))) + ) + } catch { + return [node] + } +} + +const superMethodDefinition = (superNode: Super): Method | undefined => { + const currentMethod = superNode.ancestors.find(is(Method))! + const module = getParentModule(superNode) + return module ? module.lookupMethod(currentMethod.name, superNode.args.length, { lookupStartFQN: module.fullyQualifiedName }) : undefined +} + +const getParentModule = (node: Node) => node.ancestors.find(is(Module)) + +const definedOrEmpty = (value: T | undefined): T[] => value ? [value] : [] \ No newline at end of file From 11e985cf572deea7594272c13a8ff0319cd7f2ac Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Fri, 3 May 2024 08:21:50 -0300 Subject: [PATCH 13/13] Hack to test hover --- client/src/test/hover.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/test/hover.test.ts b/client/src/test/hover.test.ts index 27701436..c6143611 100644 --- a/client/src/test/hover.test.ts +++ b/client/src/test/hover.test.ts @@ -43,6 +43,8 @@ suite('Should display on hover', () => { async function testHover(uri: Uri, position: Position, expected: any): Promise { await activate(uri) const actual = await commands.executeCommand('vscode.executeHoverProvider', uri, position) + delete actual[0]['canDecreaseHover'] + delete actual[0]['canIncreaseHover'] assert.deepEqual(actual, [expected]) assert.deepEqual( actual[0].contents.map(content => content.value),