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

Feature: Code fixes #2888

Merged
merged 32 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ef78a77
Code fixes initial implementation
timotheeguerin Feb 5, 2024
790db01
Apply code fix working
timotheeguerin Feb 5, 2024
6c7d341
better way
timotheeguerin Feb 6, 2024
0ff335e
Basics for language server
timotheeguerin Feb 6, 2024
7193e59
Comment out for now
timotheeguerin Feb 6, 2024
cf6849b
Add quick fix for number -> float64
timotheeguerin Feb 6, 2024
2c88430
alllow on linters
timotheeguerin Feb 12, 2024
524833e
Merge with main
timotheeguerin Feb 12, 2024
40bc8dd
Allow passing codefixes in linter
timotheeguerin Feb 13, 2024
87ca94b
Apply code fix and linter test helper
timotheeguerin Feb 13, 2024
c53efcb
Add codefix testing api
timotheeguerin Feb 14, 2024
cc7d429
Merge branch 'main' of https://github.com/microsoft/typespec into fea…
timotheeguerin Feb 14, 2024
08ddf25
update types and add docs
timotheeguerin Feb 14, 2024
f1df1eb
revert
timotheeguerin Feb 14, 2024
097034a
.
timotheeguerin Feb 14, 2024
050da68
Create feature-code-fixes-2024-1-14-18-23-37.md
timotheeguerin Feb 14, 2024
339294d
add
timotheeguerin Feb 14, 2024
d51dd25
code fixes test
timotheeguerin Feb 14, 2024
daaeb8f
Create feature-code-fixes-2024-1-14-19-34-9.md
timotheeguerin Feb 14, 2024
13a0146
fix
timotheeguerin Feb 14, 2024
dd49d89
Merge branch 'main' of https://github.com/Microsoft/typespec into fea…
timotheeguerin Feb 15, 2024
ebaebb1
Merge branch 'main' into feature/code-fixes
timotheeguerin Feb 15, 2024
d0ab146
Merge branch 'feature/code-fixes' of https://github.com/timotheegueri…
timotheeguerin Feb 15, 2024
8f909bf
format
timotheeguerin Feb 15, 2024
79b8780
Update docs/extending-typespec/codefixes.md
timotheeguerin Mar 1, 2024
da0e355
Update docs/extending-typespec/codefixes.md
timotheeguerin Mar 1, 2024
48e54f0
Update docs/extending-typespec/linters.md
timotheeguerin Mar 1, 2024
99b7e8a
Merge branch 'main' of https://github.com/Microsoft/typespec into fea…
timotheeguerin Mar 1, 2024
477e519
Merge branch 'main' into feature/code-fixes
timotheeguerin Mar 1, 2024
acfa7f8
Fix
timotheeguerin Mar 1, 2024
9362c7f
fix
timotheeguerin Mar 1, 2024
8c89a48
Merge branch 'main' of https://github.com/Microsoft/typespec into fea…
timotheeguerin Mar 5, 2024
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
2 changes: 1 addition & 1 deletion .vscode/launch.json
timotheeguerin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
"TYPESPEC_DEVELOPMENT_MODE": "true"
},
"presentation": {
"hidden": true
"hidden": true
}
},
{
Expand Down
18 changes: 17 additions & 1 deletion packages/compiler/src/core/checker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { $docFromComment, getIndexer } from "../lib/decorators.js";
import { createSymbol, createSymbolTable } from "./binder.js";
import { createChangeIdentifierCodeFix } from "./compiler-code-fixes/change-identifier.codefix.js";
import { getDeprecationDetails, markDeprecated } from "./deprecation.js";
import { ProjectionError, compilerAssert, reportDeprecated } from "./diagnostics.js";
import { validateInheritanceDiscriminatedUnions } from "./helpers/discriminator-utils.js";
Expand Down Expand Up @@ -33,6 +34,7 @@ import {
AugmentDecoratorStatementNode,
BooleanLiteral,
BooleanLiteralNode,
CodeFix,
DecoratedType,
Decorator,
DecoratorApplication,
Expand Down Expand Up @@ -2314,12 +2316,26 @@ export function createChecker(program: Program): Checker {

if (mapper === undefined) {
reportCheckerDiagnostic(
createDiagnostic({ code: "unknown-identifier", format: { id: node.sv }, target: node })
createDiagnostic({
code: "unknown-identifier",
format: { id: node.sv },
target: node,
codefixes: getCodefixesForUnknownIdentifier(node),
})
);
}
return undefined;
}

function getCodefixesForUnknownIdentifier(node: IdentifierNode): CodeFix[] | undefined {
switch (node.sv) {
case "number":
return [createChangeIdentifierCodeFix(node, "float64")];
default:
return undefined;
}
}

function resolveTypeReferenceSym(
node: TypeReferenceNode | MemberExpressionNode | IdentifierNode,
mapper: TypeMapper | undefined,
Expand Down
41 changes: 41 additions & 0 deletions packages/compiler/src/core/code-fixes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type {
CodeFix,
CodeFixContext,
CodeFixEdit,
PrependTextCodeFixEdit,
ReplaceTextCodeFixEdit,
SourceLocation,
} from "./types.js";
import { isArray } from "./util.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;
}

function createCodeFixContext(): CodeFixContext {
return {
prependText,
replaceText,
};

function prependText(node: SourceLocation, text: string): PrependTextCodeFixEdit {
return {
kind: "prepend-text",
pos: 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,
};
}
}
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);
},
});
}
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) };
}
1 change: 1 addition & 0 deletions packages/compiler/src/core/diagnostic-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function createDiagnosticCreator<T extends { [code: string]: DiagnosticMe
severity: diagnosticDef.severity,
message: messageStr,
target: diagnostic.target,
codefixes: diagnostic.codefixes,
};
}

Expand Down
5 changes: 5 additions & 0 deletions packages/compiler/src/core/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CharCode } from "./charcode.js";
import { formatLog } from "./logger/index.js";
import type { Program } from "./program.js";
import {
CodeFix,
Diagnostic,
DiagnosticResult,
DiagnosticTarget,
Expand Down Expand Up @@ -386,3 +387,7 @@ export function createDiagnosticCollector(): DiagnosticCollector {
export function ignoreDiagnostics<T>(result: DiagnosticResult<T>): T {
return result[0];
}

export function defineCodeFix(fix: CodeFix): CodeFix {
return fix;
}
6 changes: 6 additions & 0 deletions packages/compiler/src/core/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { validateEncodedNamesConflicts } from "../lib/encoded-names.js";
import { MANIFEST } from "../manifest.js";
import { createBinder } from "./binder.js";
import { Checker, createChecker } from "./checker.js";
import { createSuppressCodeFix } from "./compiler-code-fixes/suppress.codefix.js";
import { compilerAssert, createSourceFile } from "./diagnostics.js";
import {
resolveTypeSpecEntrypoint,
Expand Down Expand Up @@ -1047,6 +1048,11 @@ export async function compile(
return;
}

if (diagnostic.severity === "warning" && diagnostic.target !== NoTarget) {
markcowl marked this conversation as resolved.
Show resolved Hide resolved
mutate(diagnostic).codefixes ??= [];
mutate(diagnostic.codefixes).push(createSuppressCodeFix(diagnostic.target, diagnostic.code));
}

if (options.warningAsError && diagnostic.severity === "warning") {
diagnostic = { ...diagnostic, severity: "error" };
}
Expand Down
28 changes: 28 additions & 0 deletions packages/compiler/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,33 @@ export interface Diagnostic {
severity: DiagnosticSeverity;
message: string;
target: DiagnosticTarget | typeof NoTarget;
readonly codefixes?: readonly CodeFix[];
}

export interface CodeFix {
readonly id: string;
readonly label: string;
readonly fix: (fixContext: CodeFixContext) => CodeFixEdit | CodeFixEdit[] | Promise<void> | void;
}

export interface CodeFixContext {
readonly prependText: (location: SourceLocation, text: string) => PrependTextCodeFixEdit;
readonly replaceText: (location: SourceLocation, newText: string) => ReplaceTextCodeFixEdit;
}

export type CodeFixEdit = PrependTextCodeFixEdit | ReplaceTextCodeFixEdit;

export interface PrependTextCodeFixEdit {
readonly kind: "prepend-text";
readonly text: string;
readonly pos: number;
readonly file: SourceFile;
}

export interface ReplaceTextCodeFixEdit extends TextRange {
readonly kind: "replace-text";
readonly text: string;
readonly file: SourceFile;
}

/**
Expand Down Expand Up @@ -1994,6 +2021,7 @@ export type DiagnosticReportWithoutTarget<
> = {
code: C;
messageId?: M;
readonly codefixes?: readonly CodeFix[];
} & DiagnosticFormat<T, C, M>;

export type DiagnosticReport<
Expand Down
4 changes: 4 additions & 0 deletions packages/compiler/src/server/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ export const serverOptions: CompilerOptions = {
* Time in milliseconds to wait after a file change before recompiling.
*/
export const UPDATE_DEBOUNCE_TIME = 200;

export const Commands = {
APPLY_CODE_FIX: "typespec.applyCodeFix",
};
7 changes: 7 additions & 0 deletions packages/compiler/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { join } from "path";
import { fileURLToPath } from "url";
import { TextDocument } from "vscode-languageserver-textdocument";
import {
ApplyWorkspaceEditParams,
ProposedFeatures,
PublishDiagnosticsParams,
TextDocuments,
WorkspaceEdit,
createConnection,
} from "vscode-languageserver/node.js";
import { NodeHost } from "../core/node-host.js";
Expand Down Expand Up @@ -49,6 +51,9 @@ function main() {
getOpenDocumentByURL(url: string) {
return documents.get(url);
},
applyEdit(paramOrEdit: ApplyWorkspaceEditParams | WorkspaceEdit) {
return connection.workspace.applyEdit(paramOrEdit);
},
};

const s = createServer(host);
Expand Down Expand Up @@ -90,6 +95,8 @@ function main() {
connection.onDocumentHighlight(profile(s.findDocumentHighlight));
connection.onHover(profile(s.getHover));
connection.onSignatureHelp(profile(s.getSignatureHelp));
connection.onCodeAction(profile(s.getCodeActions));
connection.onExecuteCommand(profile(s.executeCommand));
connection.languages.semanticTokens.on(profile(s.buildSemanticTokens));

documents.onDidChangeContent(profile(s.checkChange));
Expand Down
Loading
Loading