Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quick fixes #205

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"autoAttachChildProcesses": true,
"preLaunchTask": {
"type": "npm",
"script": "compile"
Expand All @@ -25,7 +26,9 @@
"request": "attach",
"name": "Attach to Server",
"port": 6009,
"address": "localhost",
"restart": true,
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/out/**/*.js"]
},
{
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- DO NOT REMOVE - contributor_list:data:start:["ivojawer", "fdodino", "PalumboN", "npasserini", "dependabot[bot]", "Miranda-03", "FerRomMu"]:end -->
<!-- DO NOT REMOVE - contributor_list:data:start:["ivojawer", "PalumboN", "fdodino", "npasserini", "dependabot[bot]", "Miranda-03", "FerRomMu"]:end -->


# Wollok IDE
Expand Down Expand Up @@ -40,6 +40,6 @@ Do you want to contribute? Great, you are always welcome!
<!-- DO NOT REMOVE - contributor_list:start -->
## 👥 Contributors

<img src="https://avatars.githubusercontent.com/u/18614957?v=4" height="40" width="40" alt="ivojawer" title="ivojawer" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/4549002?v=4" height="40" width="40" alt="fdodino" title="fdodino" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/4098184?v=4" height="40" width="40" alt="PalumboN" title="PalumboN" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/4633913?v=4" height="40" width="40" alt="npasserini" title="npasserini" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/in/29110?v=4" height="40" width="40" alt="dependabot[bot]" title="dependabot[bot]" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/72475370?v=4" height="40" width="40" alt="Miranda-03" title="Miranda-03" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/70177008?v=4" height="40" width="40" alt="FerRomMu" title="FerRomMu" class="avatar circle"/>&nbsp;
<img src="https://avatars.githubusercontent.com/u/18614957?v=4" height="40" width="40" alt="ivojawer" title="ivojawer" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/4098184?v=4" height="40" width="40" alt="PalumboN" title="PalumboN" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/4549002?v=4" height="40" width="40" alt="fdodino" title="fdodino" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/4633913?v=4" height="40" width="40" alt="npasserini" title="npasserini" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/in/29110?v=4" height="40" width="40" alt="dependabot[bot]" title="dependabot[bot]" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/72475370?v=4" height="40" width="40" alt="Miranda-03" title="Miranda-03" class="avatar circle"/>&nbsp;<img src="https://avatars.githubusercontent.com/u/70177008?v=4" height="40" width="40" alt="FerRomMu" title="FerRomMu" class="avatar circle"/>&nbsp;
<!-- DO NOT REMOVE - contributor_list:end -->
<!-- prettier-ignore-end -->
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"renameProvider": {
"prepareProvider": true
},
"hoverProvider": true
"hoverProvider": true,
"codeActionProvider": true
},
"main": "./out/client/src/extension",
"configurationDefaults": {
Expand Down Expand Up @@ -400,10 +401,11 @@
"lint-staged": "lint-staged"
},
"dependencies": {
"wollok-ts": "4.2.0"
"wollok-ts": "../wollok-ts"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/node": "^18.14.1",
"@types/source-map-support": "^0",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.53.0",
Expand Down
1 change: 0 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
},
"devDependencies": {
"@types/mocha": "^10.0.1",
"@types/node": "^18.14.1",
"@types/sinon": "^10.0.13",
"@types/vscode": "^1.80.0",
"@vscode/test-electron": "^2.3.9",
Expand Down
63 changes: 63 additions & 0 deletions packages/client/src/test/code-actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as assert from 'assert'
import { commands, Range, Uri, CodeLens, CodeAction, Position, CodeActionKind, WorkspaceEdit } from 'vscode'
import { getDocumentURI, activate } from './helper'

suite('Should do code actions', () => {
const missingReferenceDoc = getDocumentURI('missingReference.wlk')
const codeActionsDoc = getDocumentURI('codeActions.wlk')

test('Gets quick fixes for missingReference', async () => {
const quickfix = new CodeAction('Import from imported.wlk', CodeActionKind.QuickFix)
quickfix.edit = new WorkspaceEdit()
quickfix.edit.insert(missingReferenceDoc, new Position(0, 0), `import imported.obj\n`)
quickfix.isPreferred = true
await testCodeActions(
missingReferenceDoc,
new Range(new Position(2, 13), new Position(2, 13)),
[quickfix]
)
})

test('Gets quick fixes for shouldDefineConstInsteadOfVar', async () => {
const quickfix = new CodeAction('Convert to const', CodeActionKind.QuickFix)
quickfix.edit = new WorkspaceEdit()
quickfix.edit.replace(codeActionsDoc, new Range(new Position(1, 2), new Position(1, 9)), `const bar`)
quickfix.isPreferred = true
await testCodeActions(
codeActionsDoc,
new Range(new Position(1, 8), new Position(1, 8)),
[quickfix]
)
})

test('Gets quick fixes for shouldNotReassignConst', async () => {
const quickfix = new CodeAction('Convert quux to var', CodeActionKind.QuickFix)
quickfix.edit = new WorkspaceEdit()
quickfix.edit.replace(codeActionsDoc, new Range(new Position(8, 4), new Position(8, 18)), `var quux = 2`)
quickfix.isPreferred = true
await testCodeActions(
codeActionsDoc,
new Range(new Position(9, 7), new Position(9, 7)),
[quickfix]
)
})
})
async function testCodeActions(
docUri: Uri,
triggerRange: Range,
expectedCodeActionList: CodeAction[],
) {
await activate(docUri)
docUri['_fsPath'] = docUri.fsPath
const actualCodeActions = (await commands.executeCommand(
'vscode.executeCodeActionProvider',
docUri,
triggerRange,
)) as CodeLens[] | null

assert.deepEqual(
actualCodeActions,
expectedCodeActionList,
'Code actions mismatch',
)
}
12 changes: 12 additions & 0 deletions packages/client/testFixture/codeActions.wlk
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
object foo{
var bar
method a() {
return bar
}
}
object baz {
method qux() {
const quux = 2
quux = 3
}
}
5 changes: 5 additions & 0 deletions packages/client/testFixture/missingReference.wlk
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object foo {
method bar(){
return obj
}
}
1 change: 0 additions & 1 deletion packages/debug-adapter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"devDependencies": {
"@types/chai": "^4",
"@types/mocha": "^10",
"@types/node": "^18.14.1",
"@types/vscode": "^1.92.0",
"@vscode/debugadapter-testsupport": "^1.67.0",
"chai": "4.3.10",
Expand Down
4 changes: 2 additions & 2 deletions packages/debug-adapter/src/test/debug-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ describe('debug adapter', function () {
}).then(async () => {
await Promise.all([
dc.assertOutput('stderr', "My exception message", 3000),
dc.waitForEvent('terminated', 2000)
dc.waitForEvent('terminated', 2000),
])
resolve("Finished")
})
Expand All @@ -202,7 +202,7 @@ describe('debug adapter', function () {
}).then(async () => {
await Promise.all([
dc.assertOutput('stdout', "Finished executing without errors", 1000),
dc.waitForEvent('terminated', 1000)
dc.waitForEvent('terminated', 1000),
])
resolve("Finished")
})
Expand Down
1 change: 0 additions & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"devDependencies": {
"@types/expect": "^24.3.0",
"@types/mocha": "^10",
"@types/node": "^18.14.1",
"@types/sinon": "^10.0.13",
"expect": "^29.7.0",
"jest": "^29.7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { completionsForNode } from './node-completion'
import { completeMessages } from './send-completion'
import { match, when } from 'wollok-ts/dist/extensions'
import { logger } from '../../utils/logger'
import { writeImportFor } from '../../utils/imports'

export const completions = (environment: Environment) => (
params: CompletionParams,
Expand Down Expand Up @@ -45,7 +46,7 @@ export const fieldCompletionItem: CompletionItemMapper<Field> = namedCompletionI

export const singletonCompletionItem: CompletionItemMapper<Singleton> = moduleCompletionItem(CompletionItemKind.Class)

export const withImport = <T extends Node>(mapper: CompletionItemMapper<T>) => (relativeTo: Node): CompletionItemMapper<T> => (node) => {
export const withImport = (mapper: CompletionItemMapper<Class | Singleton | Mixin>) => (relativeTo: Node): CompletionItemMapper<Class | Singleton | Mixin> => (node) => {
const importedPackage = node.parentPackage!
const originalPackage = relativeTo.parentPackage!

Expand All @@ -57,7 +58,7 @@ export const withImport = <T extends Node>(mapper: CompletionItemMapper<T>) => (
) {
result.detail = `Add import ${importedPackage.fileName ? relativeFilePath(packageToURI(importedPackage)) : importedPackage.name}${result.detail ? ` - ${result.detail}` : ''}`
result.additionalTextEdits = (result.additionalTextEdits ?? []).concat(
TextEdit.insert(Position.create(0, 0), `import ${importedPackage.name}.*\n`)
TextEdit.insert(Position.create(0, 0), `${writeImportFor(importedPackage)}\n`)
)
}

Expand Down
69 changes: 69 additions & 0 deletions packages/server/src/functionalities/code-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { CodeAction, CodeActionKind, CodeActionParams, Command, Diagnostic } from 'vscode-languageserver'
import { Assignment, Class, Environment, Field, Mixin, Node, possiblyReferenced, print, Problem, Reference, Singleton, validate, Variable } from 'wollok-ts'
import { writeImportFor } from '../utils/imports'
import { packageFromURI, rangeIncludes, toVSCRange, uriFromRelativeFilePath } from '../utils/text-documents'

type CodeActionResponse = Array<Command | CodeAction>

export const codeActions = (environment: Environment) => (params: CodeActionParams): CodeActionResponse => {
const problems = validate(packageFromURI(params.textDocument.uri, environment))
const problemsInRange = problems.filter(problem => rangeIncludes(toVSCRange(problem.sourceMap), params.range))
if(problemsInRange.length === 0) return null
return problemsInRange.flatMap(problem => {
const fixer = fixers[problem.code]
if (!fixer) return []
const diagnostics = matchDiagnostics(problem, params.context.diagnostics)
return fixer(problem.node).map(action => ({ ...action, diagnostics: diagnostics }))
})
}

// FIXERS //
type Fixer = (node: Node) => CodeActionResponse
const fixers: Record<string, Fixer> = {
missingReference: fixByImporting,
shouldReferenceToObjects: fixByImporting,
shouldDefineConstInsteadOfVar: (variable: Field | Variable) => changeConstantValue(variable, true, false),
shouldNotReassignConst: (assignment: Assignment) => changeConstantValue(assignment.variable.target, false, true),
}

function changeConstantValue(variable: Variable | Field, newValue: boolean, displayVarInTitle = false): CodeActionResponse {
const copiedVar = variable.copy({ isConstant: newValue })
return [{
title: `Convert ${displayVarInTitle ? variable.name + ' ' : ''}to ${newValue ? 'const' : 'var'}`,
kind: CodeActionKind.QuickFix,
isPreferred: true,
edit: {
changes: {
[uriFromRelativeFilePath(variable.sourceFileName)]: [{
newText: print(copiedVar),
range: toVSCRange(variable.sourceMap!),
}],
},
},
}]
}

function matchDiagnostics(problem: Problem, diagnostics: Diagnostic[]): Diagnostic[] {
return diagnostics.filter(diagnostic => diagnostic.code === problem.code)
}

const isImportableNode = (node: Node): node is Singleton | Class | Mixin => node.is(Singleton) || node.is(Class) || node.is(Mixin)

function fixByImporting(node: Reference<Node>): CodeActionResponse {
const targets = possiblyReferenced(node, node.environment).filter(isImportableNode)

return targets.map<CodeActionResponse[number]>(target => {
return {
title: `Import from ${target.sourceFileName}`,
kind: CodeActionKind.QuickFix,
isPreferred: true,
edit: {
changes: {
[uriFromRelativeFilePath(node.sourceFileName)]: [{
newText: `${writeImportFor(target)}\n`,
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
}],
},
},
}})
}
3 changes: 3 additions & 0 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { setWorkspaceUri, WORKSPACE_URI } from './utils/text-documents'
import { EnvironmentProvider } from './utils/vm/environment'
import { completions } from './functionalities/autocomplete/autocomplete'
import { ERROR_MISSING_WORKSPACE_FOLDER, getLSPMessage, SERVER_PROCESSING_REQUEST } from './functionalities/reporter'
import { codeActions } from './functionalities/code-actions'

export type ClientConfigurations = {
formatter: { abbreviateAssignments: boolean, maxWidth: number }
Expand Down Expand Up @@ -86,6 +87,7 @@ connection.onInitialize((params: InitializeParams) => {
renameProvider: { prepareProvider: true },
documentFormattingProvider: true,
documentRangeFormattingProvider: true,
codeActionProvider: true,
},
}
if (hasWorkspaceFolderCapability) {
Expand Down Expand Up @@ -212,6 +214,7 @@ const handlers: readonly [
[connection.onRenameRequest, rename(documents)],
[connection.onHover, typeDescriptionOnHover],
[connection.onReferences, references],
[connection.onCodeAction, codeActions],
]

try {
Expand Down
3 changes: 3 additions & 0 deletions packages/server/src/utils/imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Entity, Import, Package, print, Reference } from 'wollok-ts'

export const writeImportFor = (node: Entity): string => print(new Import({ entity: new Reference({ name: node.fullyQualifiedName }), isGeneric: node.is(Package) }))
7 changes: 7 additions & 0 deletions packages/server/src/utils/text-documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export const toVSCPosition = (position: SourceIndex): Position =>
export const toVSCRange = (sourceMap: SourceMap): Range =>
Range.create(toVSCPosition(sourceMap.start), toVSCPosition(sourceMap.end))


export function rangeIncludes(range: Range, included: Range): boolean {
const start = range.start
const end = range.end
return between(included.start, start, end) && between(included.end, start, end)
}

export const nodeToLocation = (node: Node): Location => {
if(!node.sourceFileName) throw new Error('No source file found for node')

Expand Down
14 changes: 6 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8936,7 +8936,6 @@ __metadata:
dependencies:
"@types/chai": "npm:^4"
"@types/mocha": "npm:^10"
"@types/node": "npm:^18.14.1"
"@types/vscode": "npm:^1.92.0"
"@vscode/debugadapter": "npm:^1.66.0"
"@vscode/debugadapter-testsupport": "npm:^1.67.0"
Expand All @@ -8953,7 +8952,6 @@ __metadata:
resolution: "wollok-lsp-ide-client@workspace:packages/client"
dependencies:
"@types/mocha": "npm:^10.0.1"
"@types/node": "npm:^18.14.1"
"@types/sinon": "npm:^10.0.13"
"@types/vscode": "npm:^1.80.0"
"@vscode/test-electron": "npm:^2.3.9"
Expand All @@ -8973,7 +8971,6 @@ __metadata:
dependencies:
"@types/expect": "npm:^24.3.0"
"@types/mocha": "npm:^10"
"@types/node": "npm:^18.14.1"
"@types/sinon": "npm:^10.0.13"
expect: "npm:^29.7.0"
jest: "npm:^29.7.0"
Expand All @@ -8993,6 +8990,7 @@ __metadata:
resolution: "wollok-lsp-ide@workspace:."
dependencies:
"@istanbuljs/nyc-config-typescript": "npm:^1.0.2"
"@types/node": "npm:^18.14.1"
"@types/source-map-support": "npm:^0"
"@typescript-eslint/eslint-plugin": "npm:^5.53.0"
"@typescript-eslint/parser": "npm:^5.53.0"
Expand All @@ -9009,21 +9007,21 @@ __metadata:
source-map-support: "npm:^0.5.21"
ts-node: "npm:^10.9.1"
typescript: "npm:^4.9.5"
wollok-ts: "npm:4.2.0"
wollok-ts: ../wollok-ts
yarn-run-all: "npm:^3.1.1"
languageName: unknown
linkType: soft

"wollok-ts@npm:4.2.0":
version: 4.2.0
resolution: "wollok-ts@npm:4.2.0"
"wollok-ts@file:../wollok-ts::locator=wollok-lsp-ide%40workspace%3A.":
version: 4.2.1
resolution: "wollok-ts@file:../wollok-ts#../wollok-ts::hash=50fd94&locator=wollok-lsp-ide%40workspace%3A."
dependencies:
"@types/parsimmon": "npm:^1.10.8"
parsimmon: "npm:^1.18.1"
prettier-printer: "npm:^1.1.4"
unraw: "npm:^3.0.0"
uuid: "npm:^9.0.1"
checksum: 10c0/5606c36a79ab486127122ba804e3df6c686cb4b1212c4dd64a1be30f58e4c787d50965b1625a8f4213116c32dfb955176a6d4722f03cf6bf03d0ba98af86f122
checksum: 10c0/50512b6c4ad75f5c19f0d031e55a2a1957f51eb338ad9f8d08f1f1781b2938d4d93a170761b1f07425087ef74b4be7a6be08b366230ec25862f39d238d9aa2f2
languageName: node
linkType: hard

Expand Down
Loading