Skip to content

Commit

Permalink
Code actions (#156)
Browse files Browse the repository at this point in the history
  • Loading branch information
maskmaster authored Jun 27, 2024
1 parent 9eac6c0 commit d86aa2f
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 11 deletions.
13 changes: 12 additions & 1 deletion packages/analyzer/src/issue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Position } from "@knuckles/location";
import type { Position, Range } from "@knuckles/location";

export enum AnalyzerSeverity {
Error = "error",
Expand All @@ -11,4 +11,15 @@ export interface AnalyzerIssue {
message: string;
start: Position | undefined;
end: Position | undefined;
quickFix?: AnalyzerQuickFix | undefined;
}

export interface AnalyzerQuickFix {
label?: string | undefined;
edits: AnalyzerQuickFixEdit[];
}

export interface AnalyzerQuickFixEdit {
range: Range;
text: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ export default {
check({ report, document }) {
document.visit(
(node): void => {
const regex = new RegExp(
`\\/ko\\s+${escapeStringRegexp(node.binding.name.value)}`,
);
const bindingName = node.binding.name.value;
const regex = new RegExp(`\\/ko\\s+${escapeStringRegexp(bindingName)}`);

if (!regex.test(node.endComment.content)) {
report({
Expand All @@ -21,6 +20,15 @@ export default {
severity: this.severity,
start: node.endComment.start,
end: node.endComment.end,
quickFix: {
label: "Add notation",
edits: [
{
range: node.endComment,
text: `<!-- /ko ${bindingName} -->`,
},
],
},
});
}
},
Expand Down
112 changes: 112 additions & 0 deletions packages/language-service/src/features/code-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { LanguageServiceWorker } from "../private.js";
import {
type ProtocolPosition,
type ProtocolRange,
} from "../utils/position.js";
import { Range } from "@knuckles/location";

export interface DiagnosticIdentifier {
code: string;
range: ProtocolRange;
}

export interface CodeActionParams {
fileName: string;
position: ProtocolPosition;
diagnostics?: DiagnosticIdentifier[];
}

export interface CodeAction {
label: string;
edits: CodeActionEdit[];
diagnostic?: DiagnosticIdentifier;
}

export type CodeActionEdit =
| {
type: "create-file";
fileName: string;
}
| {
type: "delete-file";
fileName: string;
}
| {
type: "rename-file";
oldFileName: string;
newFileName: string;
}
| {
type: "delete";
fileName: string;
range: ProtocolRange;
}
| {
type: "replace";
fileName: string;
range: ProtocolRange;
text: string;
}
| {
type: "insert";
fileName: string;
position: ProtocolPosition;
text: string;
};

export type CodeActions = CodeAction[];

export default async function getCodeActions(
this: LanguageServiceWorker,
params: CodeActionParams,
): Promise<CodeActions> {
const state = await this.getDocumentState(params.fileName);
if (state.broken) return [];

const codeActions: CodeActions = [];

for (const issue of state.issues) {
if (issue.quickFix) {
const diagnostic = (params.diagnostics ?? []).find((diagnostic) =>
Range.fromLinesAndColumns(
diagnostic.range.start.line,
diagnostic.range.start.column,
diagnostic.range.end.line,
diagnostic.range.end.column,
state.document.text,
),
);
const label = issue.quickFix.label ?? "Fix this issue";
const edits = issue.quickFix.edits.map((edit): CodeActionEdit => {
if (edit.text.length === 0) {
return {
type: "delete",
fileName: params.fileName,
range: edit.range,
};
} else if (edit.range.size === 0) {
return {
type: "insert",
fileName: params.fileName,
position: edit.range.start.toJSON(),
text: edit.text,
};
} else {
return {
type: "replace",
fileName: params.fileName,
range: edit.range.toJSON(),
text: edit.text,
};
}
});
codeActions.push({
label,
edits,
diagnostic,
});
}
}

return codeActions;
}
1 change: 0 additions & 1 deletion packages/language-service/src/features/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export default async function getCompletion(
includeCompletionsForImportStatements: false,
includeCompletionsForModuleExports: false,
allowRenameOfImportPath: false,
// TODO: get quote from current binding attribute
quotePreference,
triggerCharacter: params.context?.triggerCharacter as
| ts.CompletionsTriggerCharacter
Expand Down
11 changes: 5 additions & 6 deletions packages/language-service/src/features/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { LanguageServiceWorker } from "../private.js";
import type { Document } from "../utils/document.js";
import { getFullIssueRange } from "../utils/issue.js";
import type { ProtocolRange } from "../utils/position.js";
import { AnalyzerSeverity, type AnalyzerIssue } from "@knuckles/analyzer";
import { Position, Range } from "@knuckles/location";

export interface DiagnosticsParams {
fileName: string;
Expand Down Expand Up @@ -31,19 +32,17 @@ export default async function getDiagnostics(
const state = await this.getDocumentState(params.fileName);

const diagnostics = state.issues.map((issue) =>
translateIssueToDiagnostic(issue, state.document.text),
translateIssueToDiagnostic(state.document, issue),
);

return diagnostics;
}

function translateIssueToDiagnostic(
document: Document,
issue: AnalyzerIssue,
text: string,
): Diagnostic {
const start = issue.start ?? Position.fromOffset(0, text);
const end = issue.end ?? Position.fromOffset(start.offset + 1, text);
const range = new Range(start, end);
const range = getFullIssueRange(document, issue);
const severity = {
[AnalyzerSeverity.Error]: DiagnosticSeverity.Error,
[AnalyzerSeverity.Warning]: DiagnosticSeverity.Warning,
Expand Down
1 change: 1 addition & 0 deletions packages/language-service/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./public.js";
export type * from "./features/code-actions.js";
export type * from "./features/completion.js";
export type * from "./features/definition.js";
export type * from "./features/diagnostics.js";
Expand Down
2 changes: 2 additions & 0 deletions packages/language-service/src/private.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import getCodeActions from "./features/code-actions.js";
import getCompletion from "./features/completion.js";
import getDefinition from "./features/definition.js";
import getDiagnostics from "./features/diagnostics.js";
Expand Down Expand Up @@ -31,6 +32,7 @@ export class LanguageServiceWorker {
"document/definition": getDefinition.bind(this),
"document/diagnostics": getDiagnostics.bind(this),
"document/hover": getHover.bind(this),
"document/quick-fixes": getCodeActions.bind(this),
};

#programProvider = new ProgramProvider();
Expand Down
5 changes: 5 additions & 0 deletions packages/language-service/src/public.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CodeActions, CodeActionParams } from "./features/code-actions.js";
import type { Completion, CompletionParams } from "./features/completion.js";
import type { Definition, DefinitionParams } from "./features/definition.js";
import type { Diagnostics, DiagnosticsParams } from "./features/diagnostics.js";
Expand Down Expand Up @@ -129,5 +130,9 @@ export class LanguageService {
getHover(params: HoverParams): Promise<Hover | null> {
return this.#client.request("document/hover", params);
}

getCodeActions(params: CodeActionParams): Promise<CodeActions> {
return this.#client.request("document/quick-fixes", params);
}
//#endregion
}
10 changes: 10 additions & 0 deletions packages/language-service/src/utils/issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Document } from "./document.js";
import type { AnalyzerIssue } from "@knuckles/analyzer";
import { Position, Range } from "@knuckles/location";

export function getFullIssueRange(document: Document, issue: AnalyzerIssue) {
const start = issue.start ?? Position.fromOffset(0, document.text);
const end = issue.end ?? Position.fromOffset(start.offset + 1, document.text);
const range = new Range(start, end);
return range;
}
Loading

0 comments on commit d86aa2f

Please sign in to comment.