-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: optional parameters must be passed by name (#1307)
Closes #1163 ### Summary of Changes Optional parameters must now be passed by name. This allows reordering them in the stubs and inserting new optional parameters anywhere instead of just at the end. We can now also deprecate all optional parameters without introducing a new function. The error has an associated quickfix (our first). This PR also adds a data-driven test system for quickfixes and other code actions.
- Loading branch information
1 parent
790fc17
commit 674cda8
Showing
36 changed files
with
754 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Code Actions Testing | ||
|
||
Code actions tests are data-driven instead of being specified explicitly. This document explains how to add a new | ||
code action test. | ||
|
||
## Adding a code action test | ||
|
||
1. Create a new **folder** (not just a file!) in the `tests/resources/code actions` directory or any subdirectory. Give | ||
the folder a descriptive name, since the folder name becomes part of the test name. | ||
|
||
!!! tip "Skipping a test" | ||
|
||
If you want to skip a test, add the prefix `skip-` to the folder name. | ||
|
||
2. Add files with the extension `.sdsdev`, `.sds`, or `.sdsstub` **directly inside the folder**. All files in a | ||
folder will be loaded into the same workspace, so they can reference each other. Files in different folders are | ||
loaded into different workspaces, so they cannot reference each other. | ||
3. Add the Safe-DS code that you want to test to the files. | ||
4. Specify the code actions to apply using test comments (see [below](#format-of-test-comments)) at the top of the file. | ||
5. Run the tests. The test runner will automatically pick up the new test, and create a snapshot of the current output | ||
after applying the selected code actions. | ||
6. Verify that the snapshot is correct, modify it if needed, and commit it. | ||
|
||
## Format of test comments | ||
|
||
1. As usual, test comments are single-line comments that start with `$TEST$`. | ||
2. Then, the keyword `apply` follows. | ||
3. Finally, you must specify the title of the code action enclosed in double-quotes. You can also add an `r` before the | ||
opening double-quote to indicate that the title should be interpreted as a regular expression that must match the | ||
entire actual title. | ||
|
||
Here are some examples: | ||
|
||
```ts | ||
// $TEST$ apply "Remove statement." | ||
``` | ||
|
||
We apply all code actions with the exact title `Remove statement.`. | ||
|
||
```ts | ||
// $TEST$ apply r"^Remove.*" | ||
``` | ||
|
||
We apply all code actions with a title that starts with `Remove`. | ||
|
||
|
||
## Updating the snapshots | ||
|
||
To quickly update the snapshots after changes to the code generator, run `vitest` with the `--update` flag. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
packages/safe-ds-lang/src/language/codeActions/factories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { CodeAction, Diagnostic, TextEdit } from 'vscode-languageserver'; | ||
import { LangiumDocument } from 'langium'; | ||
|
||
export const createQuickfixFromTextEditsToSingleDocument = ( | ||
title: string, | ||
diagnostic: Diagnostic, | ||
document: LangiumDocument, | ||
edits: TextEdit[], | ||
isPreferred: boolean = false, | ||
): CodeAction => { | ||
return { | ||
title, | ||
kind: 'quickfix', | ||
diagnostics: [diagnostic], | ||
edit: { | ||
documentChanges: [ | ||
{ | ||
textDocument: { | ||
uri: document.textDocument.uri, | ||
version: document.textDocument.version, | ||
}, | ||
edits, | ||
}, | ||
], | ||
}, | ||
isPreferred, | ||
}; | ||
}; |
50 changes: 50 additions & 0 deletions
50
packages/safe-ds-lang/src/language/codeActions/quickfixes/arguments.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Diagnostic, TextEdit } from 'vscode-languageserver'; | ||
import { LangiumDocument } from 'langium'; | ||
import { SafeDsServices } from '../../safe-ds-module.js'; | ||
import { isSdsArgumentList, SdsArgument } from '../../generated/ast.js'; | ||
import { Argument, Parameter } from '../../helpers/nodeProperties.js'; | ||
import { SafeDsNodeMapper } from '../../helpers/safe-ds-node-mapper.js'; | ||
import { CodeActionAcceptor } from '../safe-ds-code-action-provider.js'; | ||
import { createQuickfixFromTextEditsToSingleDocument } from '../factories.js'; | ||
|
||
export const makeArgumentsAssignedToOptionalParametersNamed = (services: SafeDsServices) => { | ||
const locator = services.workspace.AstNodeLocator; | ||
const nodeMapper = services.helpers.NodeMapper; | ||
|
||
return (diagnostic: Diagnostic, document: LangiumDocument, acceptor: CodeActionAcceptor) => { | ||
const node = locator.getAstNode(document.parseResult.value, diagnostic.data.path); | ||
if (!isSdsArgumentList(node)) { | ||
/* c8 ignore next 2 */ | ||
return; | ||
} | ||
|
||
acceptor( | ||
createQuickfixFromTextEditsToSingleDocument( | ||
'Add names to arguments that are assigned to optional parameters.', | ||
diagnostic, | ||
document, | ||
node.arguments.flatMap((it) => ensureArgumentIsNamed(nodeMapper, it)), | ||
true, | ||
), | ||
); | ||
}; | ||
}; | ||
|
||
const ensureArgumentIsNamed = (nodeMapper: SafeDsNodeMapper, argument: SdsArgument): TextEdit[] | TextEdit => { | ||
const cstNode = argument.$cstNode; | ||
if (!cstNode || Argument.isNamed(argument)) { | ||
return []; | ||
} | ||
|
||
const parameter = nodeMapper.argumentToParameter(argument); | ||
if (!parameter || Parameter.isRequired(parameter)) { | ||
return []; | ||
} | ||
|
||
const text = argument.$cstNode.text; | ||
|
||
return { | ||
range: cstNode.range, | ||
newText: `${parameter.name} = ${text}`, | ||
}; | ||
}; |
34 changes: 34 additions & 0 deletions
34
packages/safe-ds-lang/src/language/codeActions/quickfixes/safe-ds-quickfix-provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { Diagnostic } from 'vscode-languageserver'; | ||
import { LangiumDocument } from 'langium'; | ||
import { CODE_ARGUMENT_POSITIONAL } from '../../validation/other/expressions/arguments.js'; | ||
import { SafeDsServices } from '../../safe-ds-module.js'; | ||
import { makeArgumentsAssignedToOptionalParametersNamed } from './arguments.js'; | ||
import { CodeActionAcceptor } from '../safe-ds-code-action-provider.js'; | ||
|
||
export class SafeDsQuickfixProvider { | ||
private readonly registry: QuickfixRegistry; | ||
|
||
constructor(services: SafeDsServices) { | ||
this.registry = { | ||
[CODE_ARGUMENT_POSITIONAL]: [makeArgumentsAssignedToOptionalParametersNamed(services)], | ||
}; | ||
} | ||
|
||
createQuickfixes(diagnostic: Diagnostic, document: LangiumDocument, acceptor: CodeActionAcceptor) { | ||
if (!diagnostic.code) { | ||
/* c8 ignore next 2 */ | ||
return; | ||
} | ||
|
||
const quickfixes = this.registry[diagnostic.code] ?? []; | ||
for (const quickfix of quickfixes) { | ||
quickfix(diagnostic, document, acceptor); | ||
} | ||
} | ||
} | ||
|
||
type QuickfixRegistry = { | ||
[code: string | number]: QuickfixCreator[]; | ||
}; | ||
|
||
type QuickfixCreator = (diagnostic: Diagnostic, document: LangiumDocument, acceptor: CodeActionAcceptor) => void; |
31 changes: 31 additions & 0 deletions
31
packages/safe-ds-lang/src/language/codeActions/safe-ds-code-action-provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { CodeActionProvider } from 'langium/lsp'; | ||
import { LangiumDocument, MaybePromise } from 'langium'; | ||
import { CancellationToken, CodeAction, CodeActionParams } from 'vscode-languageserver'; | ||
import { SafeDsServices } from '../safe-ds-module.js'; | ||
import { SafeDsQuickfixProvider } from './quickfixes/safe-ds-quickfix-provider.js'; | ||
import { isEmpty } from '../../helpers/collections.js'; | ||
|
||
export class SafeDsCodeActionProvider implements CodeActionProvider { | ||
private readonly quickfixProvider: SafeDsQuickfixProvider; | ||
|
||
constructor(services: SafeDsServices) { | ||
this.quickfixProvider = services.codeActions.QuickfixProvider; | ||
} | ||
|
||
getCodeActions( | ||
document: LangiumDocument, | ||
params: CodeActionParams, | ||
_cancelToken?: CancellationToken, | ||
): MaybePromise<CodeAction[] | undefined> { | ||
const result: CodeAction[] = []; | ||
const acceptor = (action: CodeAction) => result.push(action); | ||
|
||
for (const diagnostic of params.context.diagnostics) { | ||
this.quickfixProvider.createQuickfixes(diagnostic, document, acceptor); | ||
} | ||
|
||
return isEmpty(result) ? undefined : result; | ||
} | ||
} | ||
|
||
export type CodeActionAcceptor = (action: CodeAction) => void; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
packages/safe-ds-lang/src/language/validation/other/expressions/arguments.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { SafeDsServices } from '../../../safe-ds-module.js'; | ||
import type { SdsArgumentList } from '../../../generated/ast.js'; | ||
import { ValidationAcceptor } from 'langium'; | ||
import { Argument, getArguments, Parameter } from '../../../helpers/nodeProperties.js'; | ||
|
||
export const CODE_ARGUMENT_POSITIONAL = 'argument/positional'; | ||
|
||
export const argumentMustBeNamedIfParameterIsOptional = (services: SafeDsServices) => { | ||
const locator = services.workspace.AstNodeLocator; | ||
const nodeMapper = services.helpers.NodeMapper; | ||
|
||
return (node: SdsArgumentList, accept: ValidationAcceptor) => { | ||
for (const argument of getArguments(node).toReversed()) { | ||
const parameter = nodeMapper.argumentToParameter(argument); | ||
if (!parameter) { | ||
// Still keep going if there are extra arguments. | ||
continue; | ||
} | ||
if (Parameter.isRequired(parameter)) { | ||
// Required parameters must appear before optional parameters. | ||
return; | ||
} | ||
|
||
if (!Argument.isNamed(argument)) { | ||
accept('error', 'Argument must be named if the parameter is optional.', { | ||
node: argument, | ||
property: 'value', | ||
code: CODE_ARGUMENT_POSITIONAL, | ||
data: { path: locator.getAstNodePath(node) }, | ||
}); | ||
|
||
// Only show the error for the last argument. If users added names starting in the middle, we would no | ||
// longer be able to assign the arguments to the correct parameters. | ||
return; | ||
} | ||
} | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.