Skip to content

Commit

Permalink
Merge pull request #165 from uqbar-project/autocomplete-test-body
Browse files Browse the repository at this point in the history
Mejoras autocomplete en Body
  • Loading branch information
PalumboN authored May 29, 2024
2 parents f2b95be + 12acec8 commit 42ea358
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 57 deletions.
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 12 additions & 7 deletions server/src/functionalities/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CompletionItem, CompletionItemKind, CompletionParams, InsertTextFormat, Position, TextEdit } from 'vscode-languageserver'
import { Class, Entity, Field, Method, Mixin, Module, Name, Node, OBJECT_MODULE, Parameter, Reference, Singleton, Environment, Import, parentModule, getAllUninitializedAttributes } from 'wollok-ts'
import { Class, Entity, Field, Method, Mixin, Module, Name, Node, OBJECT_MODULE, Parameter, Reference, Singleton, Environment, Import, parentModule, getAllUninitializedAttributes, Variable } from 'wollok-ts'
import { TimeMeasurer } from '../../time-measurer'
import { cursorNode, relativeFilePath, packageToURI } from '../../utils/text-documents'
import { isNotImportedIn } from 'wollok-ts'
Expand All @@ -14,7 +14,10 @@ export const completions = (environment: Environment) => (

const { position, textDocument, context } = params
const selectionNode = cursorNode(environment, position, textDocument)

if(!selectionNode) {
timeMeasurer.finalReport()
throw new Error('Could not find selection node')
}
timeMeasurer.addTime(`Autocomplete - ${selectionNode?.kind}`)

const autocompleteMessages = context?.triggerCharacter === '.' && !selectionNode.parent.is(Import)
Expand All @@ -32,9 +35,11 @@ export const completions = (environment: Environment) => (
// -----------------
type CompletionItemMapper<T extends Node> = (node: T) => CompletionItem

export const parameterCompletionItem: CompletionItemMapper<Parameter> = namedCompletionItem(CompletionItemKind.Variable)
export const parameterCompletionItem: CompletionItemMapper<Parameter> = namedCompletionItem(CompletionItemKind.Variable, '006')

export const variableCompletionItem: CompletionItemMapper<Variable> = namedCompletionItem(CompletionItemKind.Variable, '003')

export const fieldCompletionItem: CompletionItemMapper<Field> = namedCompletionItem(CompletionItemKind.Field)
export const fieldCompletionItem: CompletionItemMapper<Field> = namedCompletionItem(CompletionItemKind.Field, '009')

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

Expand Down Expand Up @@ -110,17 +115,17 @@ export const methodCompletionItem = (node: Node, method: Method): CompletionItem


function moduleCompletionItem<T extends Module>(kind: CompletionItemKind){
return (module: T) => namedCompletionItem(kind)(module.name ? module as {name: Name} : { name: 'unnamed' })
return (module: T) => namedCompletionItem(kind, '012')(module.name ? module as {name: Name} : { name: 'unnamed' })
}

function namedCompletionItem<T extends {name: string}>(kind: CompletionItemKind) {
function namedCompletionItem<T extends {name: string}>(kind: CompletionItemKind, sortText = '999') {
return (namedNode: T): CompletionItem => {
return {
label: namedNode.name,
insertText: namedNode.name,
insertTextFormat: InsertTextFormat.PlainText,
kind,
sortText: '001',
sortText,
}
}
}
Expand Down
24 changes: 16 additions & 8 deletions server/src/functionalities/autocomplete/node-completion.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
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 { Body, Class, Describe, Entity, Environment, Import, Method, Mixin, New, Node, Package, Program, Reference, Singleton, Test, Variable, implicitImport, match, parentImport, when } from 'wollok-ts'
import { logger } from '../../utils/logger'
import { classCompletionItem, entityCompletionItem, fieldCompletionItem, initializerCompletionItem, parameterCompletionItem, singletonCompletionItem, variableCompletionItem, withImport } from './autocomplete'
import { optionAsserts, optionConstReferences, optionDescribes, optionImports, optionInitialize, optionMethods, optionModules, optionPrograms, optionPropertiesAndReferences, optionReferences, optionTests } from './options-autocomplete'

export const completionsForNode = (node: Node): CompletionItem[] => {
try {
Expand Down Expand Up @@ -40,25 +40,31 @@ const completeProgram = (): CompletionItem[] => [
...optionReferences,
]

const completeTest = (): CompletionItem[] => [
const completeTest = (node: Test): CompletionItem[] => [
...optionReferences,
...optionAsserts,
...node.parent.is(Describe) ? node.parent.allFields.map(fieldCompletionItem) : [],
...completeAllSigletons(node),
]

const completeModule = (): CompletionItem[] => [
...optionPropertiesAndReferences,
...optionMethods,
]

const completeBody = (node: Body): CompletionItem[] => completeForParent(node)
const completeBody = (node: Body): CompletionItem[] => [
...completeForParent(node),
...node.scope.localContributions()
.filter((value) => value[1].is(Variable))
.map((value) => variableCompletionItem(value[1] as Variable)),
]

const completeMethod = (node: Method): CompletionItem[] => {
const parent = node.parent
const fields = is(Module) ? parent.fields : []
return [
...node.parameters.map(parameterCompletionItem),
...fields.map(fieldCompletionItem),
...(node.environment.descendants.filter(node => node.is(Singleton) && !!node.name) as Singleton[]).map(withImport(singletonCompletionItem)(node)),
...parent.fields.map(fieldCompletionItem),
...completeAllSigletons(node),
]
}

Expand All @@ -82,3 +88,5 @@ const completeNew = (node: New): CompletionItem[] =>
const availableForImport = (node: Node) => (node.is(Class) || node.is(Singleton) || node.is(Reference) || node.is(Mixin)) && node.name && (node as Entity).fullyQualifiedName && !implicitImport(node)

const completeImports = (node: Import) => (node.environment.descendants.filter(availableForImport) as Entity[]).map(entityCompletionItem)

const completeAllSigletons = (originNode: Node) => (originNode.environment.descendants.filter(node => node.is(Singleton) && !node.isClosure()) as Singleton[]).map(withImport(singletonCompletionItem)(originNode))
6 changes: 5 additions & 1 deletion server/src/functionalities/references.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Location, ReferenceParams } from 'vscode-languageserver'
import { Environment, Method, mayExecute, targettingAt } from 'wollok-ts'
import { cursorNode, nodeToLocation } from '../utils/text-documents'
import { logger } from '../utils/logger'

export const references = (environment: Environment) => (params: ReferenceParams): Location[] | null => {
const node = cursorNode(environment, params.position, params.textDocument)

if (!node) {
logger.error('✘ Could not find the node to search references for')
return null
}
return environment.descendants.filter(
node.is(Method) ?
mayExecute(node) :
Expand Down
70 changes: 40 additions & 30 deletions server/src/test/autocomplete.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { expect } from 'expect'
import { CompletionItem } from 'vscode-languageserver'
import { Body, Class, Describe, Environment, Field, Import, Literal, Method, Mixin, New, Node, Package, Program, Reference, Sentence, Singleton, buildEnvironment, link } from 'wollok-ts'
import { Body, Describe, Environment, Field, Import, Literal, Method, New, Node, Package, Program, Reference, Sentence, Singleton, buildEnvironment, link } from 'wollok-ts'
import { completeForParent, completionsForNode } from '../functionalities/autocomplete/node-completion'
import { completeMessages } from '../functionalities/autocomplete/send-completion'
import { buildPepitaEnvironment } from './utils/wollok-test-utils'

describe('autocomplete', () => {
it('completions for body should be local variables plus the parents completions', () => {
const environment = buildPepitaEnvironment()
const body = (environment.getNodeByFQN('pepita.pepita') as Singleton).allMethods[0].body as Body
const completions = completionsForNodeSorted(body)
expect(
completions.map(completion => completion.label))
.toEqual(['gramos', ...completeForParent(body).map(completion => completion.label)]
)
})

describe('completions for singleton node', () => {
let pepitaEnvironment: Environment
let pepita: Singleton
Expand All @@ -23,9 +33,9 @@ describe('autocomplete', () => {
testCompletionLabelsForNode(pepita, ['var attribute', 'var property', 'const attribute', 'const property', 'method (effect)', 'method (return)'])
})

it('method should complete with module fields, parameters and WKOs', () => {
const comerMethod = pepita.lookupMethod('comer', 1)
testCompletionLabelsForNodeIncludes(comerMethod!, ['comida', 'peso', 'game', 'pepita', 'keyboard', 'assert', 'console'])
it('method should complete with variables, module fields, parameters and WKOs', () => {
const comerMethod = pepita.lookupMethod('comer', 1)!.body as Body
testCompletionLabelsForNodeIncludes(comerMethod!, ['gramos', 'comida', 'peso', 'game', 'pepita', 'keyboard', 'assert', 'console'])
})

it('unhandled node should complete with parent completions', () => {
Expand All @@ -45,17 +55,10 @@ describe('autocomplete', () => {
const unhandledNodeMock: Node = { kind: 'UnhandledNode' } as unknown as Node
expect(() => completeForParent(unhandledNodeMock)).toThrow('Node has no parent')
})

it('body should complete with parent completions', () => {
const comer = pepita.lookupMethod('comer', 1)!
const body = comer.body! as Body
expect(completionsForNodeSorted(body)).toEqual(completeForParent(body))
})
})

describe('completions for class node', () => {
let environment: Environment
let birdClass: Class
const fileName = 'completeUnitClass.wlk'
const className = 'completeUnitClass.Bird'

Expand All @@ -69,23 +72,15 @@ describe('autocomplete', () => {
}
` }])
birdClass = environment.getNodeByFQN<Class>(className)
})

it('class should complete with snippets', () => {
testCompletionLabelsForNode(environment.getNodeByFQN(className), ['var attribute', 'var property', 'const attribute', 'const property', 'method (effect)', 'method (return)'])
})

it('body should complete with parent completions', () => {
const fly = birdClass.lookupMethod('fly', 1)!
const body = fly.body! as Body
expect(completionsForNodeSorted(body)).toEqual(completeForParent(body))
})
})

describe('completions for mixin node', () => {
let environment: Environment
let aMixin: Mixin
const fileName = 'completeUnitMixin.wlk'
const mixinName = 'completeUnitMixin.Flier'

Expand All @@ -100,18 +95,11 @@ describe('autocomplete', () => {
}
` }])
aMixin = environment.getNodeByFQN<Mixin>(mixinName)
})

it('mixin should complete with snippets', () => {
testCompletionLabelsForNode(environment.getNodeByFQN(mixinName), ['var attribute', 'var property', 'const attribute', 'const property', 'method (effect)', 'method (return)'])
})

it('body should complete with parent completions', () => {
const fly = aMixin.lookupMethod('fly', 1)!
const body = fly.body! as Body
expect(completionsForNodeSorted(body)).toEqual(completeForParent(body))
})
})

describe('completions for describe & tests nodes', () => {
Expand All @@ -123,9 +111,9 @@ describe('autocomplete', () => {
beforeEach(() => {
environment = buildEnvironment([{ name: fileName, content: `
describe "group" {
var aField = "bar"
test "basic" {
var aVar = "baz"
}
}
Expand All @@ -139,8 +127,30 @@ describe('autocomplete', () => {
})

it('test should complete with snippets', () => {
const firstTest = aDescribe.tests[0]
testCompletionLabelsForNode(firstTest, ['var attribute', 'const attribute', 'assert equality', 'assert boolean', 'assert throws', 'assert throws message'])
const firstTest = aDescribe.tests[0].body
testCompletionLabelsForNode(firstTest, [
// local variables
'aVar',
// parent fields
'aField',
// WKOs
'game',
'keyboard',
'void',
'collection',
'calendar',
'io',
'console',
'assert',
'runtime',
// snippets
'var attribute',
'const attribute',
'assert equality',
'assert boolean',
'assert throws',
'assert throws message',
])
})
})

Expand Down
3 changes: 2 additions & 1 deletion server/src/test/mocks/file-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const pepitaFile = `object pepita {
var peso = 0
method comer(comida){
peso = peso + comida.calorias()
var gramos = comida.calorias()
peso = peso + gramos
}
}`
2 changes: 1 addition & 1 deletion server/src/utils/text-documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function cursorNode(
environment: Environment,
position: Position,
textDocument: TextDocumentIdentifier
): Node {
): Node | undefined {
return getNodesByPosition(environment, {
position,
textDocument,
Expand Down

0 comments on commit 42ea358

Please sign in to comment.