forked from microsoft/typespec
-
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.
Feature: Code fixes (microsoft#2888)
closes microsoft#615 ## Code fixes added ### Suppress ![Kapture 2024-02-05 at 15 16 22](https://github.com/microsoft/typespec/assets/1031227/644014a3-9352-4bd4-b1b8-0d314c627405) ### `number` -> `float64` typo fix ![Kapture 2024-02-06 at 09 50 28](https://github.com/microsoft/typespec/assets/1031227/65b2e9aa-c510-440f-a1c6-7851611b65a2) ### Enum to extensible enum in typespec-azure Azure/typespec-azure#258 --------- Co-authored-by: Mark Cowlishaw <[email protected]>
- Loading branch information
1 parent
abba29c
commit 6a9c62a
Showing
32 changed files
with
936 additions
and
61 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking | ||
changeKind: feature | ||
packages: | ||
- "@typespec/compiler" | ||
--- | ||
|
||
Add support for codefixes |
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,8 @@ | ||
--- | ||
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking | ||
changeKind: fix | ||
packages: | ||
- "@typespec/playground" | ||
--- | ||
|
||
Refactor of the mapping between Language Server and monaco API |
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,78 @@ | ||
--- | ||
title: Code fixes | ||
--- | ||
|
||
## Define a code fix | ||
|
||
A code fix can be defined using the `defineCodeFix` function which is just here to help with typing. It doesn't need to be declared separately from being reported but doing so allows you to test it. | ||
|
||
A codefix takes 3 arguments: | ||
|
||
- `id`: A unique identifier for the code fix. | ||
- `label`: A human-readable label for the code fix. | ||
- `fix`: The implementation of the codefix. Takes in a context that allows patch operations on the source code. The fix function should return the list of changes to apply to the source code. | ||
|
||
```ts | ||
import { defineCodeFix, getSourceLocation, type IdentifierNode } from "@typespec/compiler"; | ||
|
||
export function createChangeIdentifierCodeFix(node: IdentifierNode, newIdentifier: string) { | ||
return defineCodeFix({ | ||
id: "change-identifier", | ||
label: `Change ${node.sv} to ${newIdentifier}`, | ||
fix: (context) => { | ||
const location = getSourceLocation(node); | ||
return context.replaceText(location, newIdentifier); | ||
}, | ||
}); | ||
} | ||
``` | ||
|
||
## Connect a codefix to a diagnostic | ||
|
||
When reporting diagnostics, you can pass `codefixes` to the `reportDiagnostic`/`createDiagnostic` functions. It is an array of codefixes that can be used to fix the diagnostic. | ||
|
||
```ts | ||
reportDiagnostic({ | ||
code: "invalid-identifier", | ||
target: node, | ||
codefixes: [createChangeIdentifierCodeFix(node, "string")], | ||
}); | ||
``` | ||
|
||
## Test a diagnostic | ||
|
||
[See here for testing a codefix inside a linter rule](./linters.md#test-a-codefix) | ||
|
||
Testing a codefix is done by using the `expectCodeFixOnAst` function from the `@typespec/compiler/testing` package. It takes in the source code and a function that returns the codefix to apply. | ||
It takes the input source code with a cursor defined by `┆` which will be used to resolve the node where the codefix should be applied. The callback function will receive that resolved node and is expected to return the codefix to test. | ||
|
||
:::note | ||
When using multi-line strings (with `\``) in typescript there is no de-indenting done so you will need to make sure the input and expected result are aligned to the left. | ||
::: | ||
|
||
```ts | ||
import { strictEqual } from "assert"; | ||
import { createChangeIdentifierCodeFix } from "./change-identifier.codefix.js"; | ||
import { SyntaxKind } from "@typespec/compiler"; | ||
import { expectCodeFixOnAst } from "@typespec/compiler/testing"; | ||
|
||
describe("CodeFix: change-identifier", () => { | ||
it("it change identifier", async () => { | ||
await expectCodeFixOnAst( | ||
` | ||
model Foo { | ||
a: ┆number; | ||
} | ||
`, | ||
(node) => { | ||
strictEqual(node.kind, SyntaxKind.Identifier); | ||
return createChangeIdentifierCodeFix(node, "int32"); | ||
} | ||
).toChangeTo(` | ||
model Foo { | ||
a: int32; | ||
} | ||
`); | ||
}); | ||
}); | ||
``` |
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,97 @@ | ||
import { isArray } from "../utils/misc.js"; | ||
import type { | ||
CodeFix, | ||
CodeFixContext, | ||
CodeFixEdit, | ||
CompilerHost, | ||
FilePos, | ||
InsertTextCodeFixEdit, | ||
ReplaceTextCodeFixEdit, | ||
SourceFile, | ||
SourceLocation, | ||
} from "./types.js"; | ||
|
||
export async function resolveCodeFix(codeFix: CodeFix): Promise<CodeFixEdit[]> { | ||
const context = createCodeFixContext(); | ||
const values = await codeFix.fix(context); | ||
const textEdit = values === undefined ? [] : isArray(values) ? values : [values]; | ||
return textEdit; | ||
} | ||
|
||
export async function applyCodeFix(host: CompilerHost, codeFix: CodeFix) { | ||
const edits = await resolveCodeFix(codeFix); | ||
await applyCodeFixEdits(host, edits); | ||
} | ||
|
||
async function applyCodeFixEdits(host: CompilerHost, edits: CodeFixEdit[]) { | ||
const perFile = new Map<string, [SourceFile, CodeFixEdit[]]>(); | ||
|
||
for (const edit of edits) { | ||
const file = edit.file; | ||
if (!perFile.has(file.path)) { | ||
perFile.set(file.path, [file, []]); | ||
} | ||
perFile.get(file.path)![1].push(edit); | ||
} | ||
|
||
for (const [file, edits] of perFile.values()) { | ||
const newContent = applyCodeFixEditsOnText(file.text, edits); | ||
await host.writeFile(file.path, newContent); | ||
} | ||
} | ||
|
||
function applyCodeFixEditsOnText(content: string, edits: CodeFixEdit[]): string { | ||
const segments = []; | ||
let last = 0; | ||
for (const edit of edits) { | ||
switch (edit.kind) { | ||
case "insert-text": | ||
segments.push(content.slice(last, edit.pos)); | ||
segments.push(edit.text); | ||
last = edit.pos; | ||
break; | ||
case "replace-text": | ||
segments.push(content.slice(last, edit.pos)); | ||
segments.push(edit.text); | ||
last = edit.end; | ||
} | ||
} | ||
segments.push(content.slice(last)); | ||
return segments.join(""); | ||
} | ||
|
||
function createCodeFixContext(): CodeFixContext { | ||
return { | ||
prependText, | ||
appendText, | ||
replaceText, | ||
}; | ||
|
||
function prependText(node: SourceLocation | FilePos, text: string): InsertTextCodeFixEdit { | ||
return { | ||
kind: "insert-text", | ||
pos: node.pos, | ||
text, | ||
file: node.file, | ||
}; | ||
} | ||
|
||
function appendText(node: SourceLocation | FilePos, text: string): InsertTextCodeFixEdit { | ||
return { | ||
kind: "insert-text", | ||
pos: "end" in node ? node.end : node.pos, | ||
text, | ||
file: node.file, | ||
}; | ||
} | ||
|
||
function replaceText(node: SourceLocation, text: string): ReplaceTextCodeFixEdit { | ||
return { | ||
kind: "replace-text", | ||
pos: node.pos, | ||
end: node.end, | ||
file: node.file, | ||
text, | ||
}; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
packages/compiler/src/core/compiler-code-fixes/change-identifier.codefix.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,13 @@ | ||
import { defineCodeFix, getSourceLocation } from "../diagnostics.js"; | ||
import type { IdentifierNode } from "../types.js"; | ||
|
||
export function createChangeIdentifierCodeFix(node: IdentifierNode, newIdentifier: string) { | ||
return defineCodeFix({ | ||
id: "change-identifier", | ||
label: `Change ${node.sv} to ${newIdentifier}`, | ||
fix: (context) => { | ||
const location = getSourceLocation(node); | ||
return context.replaceText(location, newIdentifier); | ||
}, | ||
}); | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/compiler/src/core/compiler-code-fixes/suppress.codefix.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 { isWhiteSpace } from "../charcode.js"; | ||
import { defineCodeFix, getSourceLocation } from "../diagnostics.js"; | ||
import type { DiagnosticTarget, SourceLocation } from "../types.js"; | ||
|
||
export function createSuppressCodeFix(diagnosticTarget: DiagnosticTarget, warningCode: string) { | ||
return defineCodeFix({ | ||
id: "suppress", | ||
label: `Suppress warning: "${warningCode}"`, | ||
fix: (context) => { | ||
const location = getSourceLocation(diagnosticTarget); | ||
const { lineStart, indent } = findLineStartAndIndent(location); | ||
const updatedLocation = { ...location, pos: lineStart }; | ||
return context.prependText(updatedLocation, `${indent}#suppress "${warningCode}" ""\n`); | ||
}, | ||
}); | ||
} | ||
|
||
function findLineStartAndIndent(location: SourceLocation): { lineStart: number; indent: string } { | ||
const text = location.file.text; | ||
let pos = location.pos; | ||
let indent = 0; | ||
while (pos > 0 && text[pos - 1] !== "\n") { | ||
if (isWhiteSpace(text.charCodeAt(pos - 1))) { | ||
indent++; | ||
} else { | ||
indent = 0; | ||
} | ||
pos--; | ||
} | ||
return { lineStart: pos, indent: location.file.text.slice(pos, pos + indent) }; | ||
} |
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
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.