@alexgorbatchev/typescript-ai-policy 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -37
- package/package.json +1 -1
- package/src/oxlint/createOxlintConfig.ts +1 -0
- package/src/oxlint/plugin.ts +2 -0
- package/src/oxlint/rules/no-i-prefixed-type-aliases.ts +45 -0
- package/src/oxlint/rules/require-template-indent.ts +46 -9
- package/src/oxlint/rules/testid-naming-convention.ts +1 -1
- package/src/semantic-fixes/applyFileChanges.ts +6 -6
- package/src/semantic-fixes/applySemanticFixes.ts +29 -26
- package/src/semantic-fixes/applyTextEdits.ts +20 -20
- package/src/semantic-fixes/backends/tsgo-lsp/TsgoLspClient.ts +49 -49
- package/src/semantic-fixes/backends/tsgo-lsp/createTsgoLspSemanticFixBackend.ts +41 -41
- package/src/semantic-fixes/providers/createInterfaceNamingConventionSemanticFixProvider.ts +15 -89
- package/src/semantic-fixes/providers/createNoIPrefixedTypeAliasesSemanticFixProvider.ts +49 -0
- package/src/semantic-fixes/providers/createTestFileLocationConventionSemanticFixProvider.ts +9 -19
- package/src/semantic-fixes/providers/helpers.ts +117 -0
- package/src/semantic-fixes/readMovedFileTextEdits.ts +7 -7
- package/src/semantic-fixes/readSemanticFixRuntimePaths.ts +2 -2
- package/src/semantic-fixes/runApplySemanticFixes.ts +5 -5
- package/src/semantic-fixes/runOxlintJson.ts +9 -9
- package/src/semantic-fixes/types.ts +35 -41
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import ts from "typescript";
|
|
3
|
-
import type {
|
|
3
|
+
import type { TextEdit } from "./types.ts";
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type OffsetTextEdit = {
|
|
6
6
|
endOffset: number;
|
|
7
7
|
newText: string;
|
|
8
8
|
startOffset: number;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
type
|
|
11
|
+
type FileEditEntry = {
|
|
12
12
|
filePath: string;
|
|
13
|
-
textEdits: readonly
|
|
13
|
+
textEdits: readonly TextEdit[];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
function readFileEditEntries(textEdits: readonly
|
|
17
|
-
const textEditsByFilePath = new Map<string,
|
|
16
|
+
function readFileEditEntries(textEdits: readonly TextEdit[]): readonly FileEditEntry[] {
|
|
17
|
+
const textEditsByFilePath = new Map<string, TextEdit[]>();
|
|
18
18
|
|
|
19
19
|
for (const textEdit of textEdits) {
|
|
20
20
|
const existingTextEdits = textEditsByFilePath.get(textEdit.filePath);
|
|
@@ -32,7 +32,7 @@ function readFileEditEntries(textEdits: readonly ITextEdit[]): readonly IFileEdi
|
|
|
32
32
|
}));
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function compareOffsetTextEditsAscending(left:
|
|
35
|
+
function compareOffsetTextEditsAscending(left: OffsetTextEdit, right: OffsetTextEdit): number {
|
|
36
36
|
if (left.startOffset !== right.startOffset) {
|
|
37
37
|
return left.startOffset - right.startOffset;
|
|
38
38
|
}
|
|
@@ -44,7 +44,7 @@ function compareOffsetTextEditsAscending(left: IOffsetTextEdit, right: IOffsetTe
|
|
|
44
44
|
return left.newText.localeCompare(right.newText);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function compareOffsetTextEditsDescending(left:
|
|
47
|
+
function compareOffsetTextEditsDescending(left: OffsetTextEdit, right: OffsetTextEdit): number {
|
|
48
48
|
return compareOffsetTextEditsAscending(right, left);
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -53,7 +53,7 @@ function readOffset(positionContent: string, line: number, character: number): n
|
|
|
53
53
|
return ts.getPositionOfLineAndCharacter(sourceFile, line, character);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function readOffsetTextEdit(content: string, textEdit:
|
|
56
|
+
function readOffsetTextEdit(content: string, textEdit: TextEdit): OffsetTextEdit {
|
|
57
57
|
return {
|
|
58
58
|
endOffset: readOffset(content, textEdit.end.line, textEdit.end.character),
|
|
59
59
|
newText: textEdit.newText,
|
|
@@ -61,31 +61,31 @@ function readOffsetTextEdit(content: string, textEdit: ITextEdit): IOffsetTextEd
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function haveSameRange(left:
|
|
64
|
+
function haveSameRange(left: OffsetTextEdit, right: OffsetTextEdit): boolean {
|
|
65
65
|
return left.startOffset === right.startOffset && left.endOffset === right.endOffset;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function isInsertion(edit:
|
|
68
|
+
function isInsertion(edit: OffsetTextEdit): boolean {
|
|
69
69
|
return edit.startOffset === edit.endOffset;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function haveSameInsertionPoint(left:
|
|
72
|
+
function haveSameInsertionPoint(left: OffsetTextEdit, right: OffsetTextEdit): boolean {
|
|
73
73
|
return isInsertion(left) && isInsertion(right) && left.startOffset === right.startOffset;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function rangesOverlap(left:
|
|
76
|
+
function rangesOverlap(left: OffsetTextEdit, right: OffsetTextEdit): boolean {
|
|
77
77
|
return left.startOffset < right.endOffset && right.startOffset < left.endOffset;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function containsRange(container:
|
|
80
|
+
function containsRange(container: OffsetTextEdit, candidate: OffsetTextEdit): boolean {
|
|
81
81
|
return container.startOffset <= candidate.startOffset && container.endOffset >= candidate.endOffset;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function readNormalizedOffsetTextEdits(
|
|
85
85
|
filePath: string,
|
|
86
|
-
offsetTextEdits: readonly
|
|
87
|
-
): readonly
|
|
88
|
-
const normalizedOffsetTextEdits:
|
|
86
|
+
offsetTextEdits: readonly OffsetTextEdit[],
|
|
87
|
+
): readonly OffsetTextEdit[] {
|
|
88
|
+
const normalizedOffsetTextEdits: OffsetTextEdit[] = [];
|
|
89
89
|
|
|
90
90
|
for (const offsetTextEdit of [...offsetTextEdits].sort(compareOffsetTextEditsAscending)) {
|
|
91
91
|
const previousOffsetTextEdit = normalizedOffsetTextEdits.at(-1);
|
|
@@ -123,7 +123,7 @@ function readNormalizedOffsetTextEdits(
|
|
|
123
123
|
return normalizedOffsetTextEdits;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
export function readUpdatedContent(filePath: string, content: string, textEdits: readonly
|
|
126
|
+
export function readUpdatedContent(filePath: string, content: string, textEdits: readonly TextEdit[]): string {
|
|
127
127
|
const offsetTextEdits = [
|
|
128
128
|
...readNormalizedOffsetTextEdits(
|
|
129
129
|
filePath,
|
|
@@ -143,7 +143,7 @@ export function readUpdatedContent(filePath: string, content: string, textEdits:
|
|
|
143
143
|
return updatedContent;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
function applyFileTextEdits(filePath: string, textEdits: readonly
|
|
146
|
+
function applyFileTextEdits(filePath: string, textEdits: readonly TextEdit[]): void {
|
|
147
147
|
const content = readFileSync(filePath, "utf8");
|
|
148
148
|
const updatedContent = readUpdatedContent(filePath, content, textEdits);
|
|
149
149
|
|
|
@@ -152,7 +152,7 @@ function applyFileTextEdits(filePath: string, textEdits: readonly ITextEdit[]):
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
export function applyTextEdits(textEdits: readonly
|
|
155
|
+
export function applyTextEdits(textEdits: readonly TextEdit[]): readonly string[] {
|
|
156
156
|
const changedFilePaths: string[] = [];
|
|
157
157
|
|
|
158
158
|
for (const fileEditEntry of readFileEditEntries(textEdits)) {
|
|
@@ -1,95 +1,95 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { once } from "node:events";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
-
import type {
|
|
4
|
+
import type { LineAndCharacter } from "../../types.ts";
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
type JsonRpcIdentifier = number | string | null;
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type JsonRpcError = {
|
|
9
9
|
code: number;
|
|
10
10
|
message: string;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
type
|
|
14
|
-
error:
|
|
15
|
-
id:
|
|
13
|
+
type JsonRpcErrorResponse = {
|
|
14
|
+
error: JsonRpcError;
|
|
15
|
+
id: JsonRpcIdentifier;
|
|
16
16
|
jsonrpc: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
type
|
|
20
|
-
id?:
|
|
19
|
+
type JsonRpcRequestMessage = {
|
|
20
|
+
id?: JsonRpcIdentifier;
|
|
21
21
|
jsonrpc: string;
|
|
22
22
|
method: string;
|
|
23
23
|
params?: unknown;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
type
|
|
27
|
-
id:
|
|
26
|
+
type JsonRpcSuccessResponse = {
|
|
27
|
+
id: JsonRpcIdentifier;
|
|
28
28
|
jsonrpc: string;
|
|
29
29
|
result?: unknown;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
type
|
|
32
|
+
type JsonRpcResponse = JsonRpcErrorResponse | JsonRpcSuccessResponse;
|
|
33
33
|
|
|
34
|
-
type
|
|
34
|
+
type LspTextDocumentIdentifier = {
|
|
35
35
|
uri: string;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
type
|
|
38
|
+
type LspPosition = {
|
|
39
39
|
character: number;
|
|
40
40
|
line: number;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
type
|
|
43
|
+
type LspWorkspaceFolder = {
|
|
44
44
|
name: string;
|
|
45
45
|
uri: string;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
type
|
|
48
|
+
type LspWorkspaceEditCapabilities = {
|
|
49
49
|
documentChanges: boolean;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
type
|
|
53
|
-
workspaceEdit:
|
|
52
|
+
type LspWorkspaceCapabilities = {
|
|
53
|
+
workspaceEdit: LspWorkspaceEditCapabilities;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
type
|
|
56
|
+
type LspRenameCapabilities = {
|
|
57
57
|
prepareSupport: boolean;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
type
|
|
61
|
-
rename:
|
|
60
|
+
type LspTextDocumentCapabilities = {
|
|
61
|
+
rename: LspRenameCapabilities;
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
type
|
|
65
|
-
textDocument:
|
|
66
|
-
workspace:
|
|
64
|
+
type LspClientCapabilities = {
|
|
65
|
+
textDocument: LspTextDocumentCapabilities;
|
|
66
|
+
workspace: LspWorkspaceCapabilities;
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
type
|
|
70
|
-
capabilities:
|
|
69
|
+
type LspInitializeParams = {
|
|
70
|
+
capabilities: LspClientCapabilities;
|
|
71
71
|
processId: number;
|
|
72
72
|
rootUri: string;
|
|
73
|
-
workspaceFolders: readonly
|
|
73
|
+
workspaceFolders: readonly LspWorkspaceFolder[];
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
type
|
|
77
|
-
position:
|
|
78
|
-
textDocument:
|
|
76
|
+
type LspPrepareRenameParams = {
|
|
77
|
+
position: LspPosition;
|
|
78
|
+
textDocument: LspTextDocumentIdentifier;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
type
|
|
81
|
+
type LspRenameParams = {
|
|
82
82
|
newName: string;
|
|
83
|
-
position:
|
|
84
|
-
textDocument:
|
|
83
|
+
position: LspPosition;
|
|
84
|
+
textDocument: LspTextDocumentIdentifier;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
type
|
|
87
|
+
type TsgoLspClientOptions = {
|
|
88
88
|
tsgoExecutablePath: string;
|
|
89
89
|
workspacePath: string;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
type
|
|
92
|
+
type PendingRequest = {
|
|
93
93
|
reject: (error: Error) => void;
|
|
94
94
|
resolve: (result: unknown) => void;
|
|
95
95
|
};
|
|
@@ -98,7 +98,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
98
98
|
return typeof value === "object" && value !== null;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function isJsonRpcError(value: unknown): value is
|
|
101
|
+
function isJsonRpcError(value: unknown): value is JsonRpcError {
|
|
102
102
|
if (!isRecord(value)) {
|
|
103
103
|
return false;
|
|
104
104
|
}
|
|
@@ -106,7 +106,7 @@ function isJsonRpcError(value: unknown): value is IJsonRpcError {
|
|
|
106
106
|
return typeof value.code === "number" && typeof value.message === "string";
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function isJsonRpcRequestMessage(value: unknown): value is
|
|
109
|
+
function isJsonRpcRequestMessage(value: unknown): value is JsonRpcRequestMessage {
|
|
110
110
|
if (!isRecord(value)) {
|
|
111
111
|
return false;
|
|
112
112
|
}
|
|
@@ -114,7 +114,7 @@ function isJsonRpcRequestMessage(value: unknown): value is IJsonRpcRequestMessag
|
|
|
114
114
|
return value.jsonrpc === "2.0" && typeof value.method === "string";
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function isJsonRpcResponse(value: unknown): value is
|
|
117
|
+
function isJsonRpcResponse(value: unknown): value is JsonRpcResponse {
|
|
118
118
|
if (!isRecord(value)) {
|
|
119
119
|
return false;
|
|
120
120
|
}
|
|
@@ -135,7 +135,7 @@ function isJsonRpcResponse(value: unknown): value is IJsonRpcResponse {
|
|
|
135
135
|
return Reflect.has(value, "result");
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function readLspPosition(position:
|
|
138
|
+
function readLspPosition(position: LineAndCharacter): LspPosition {
|
|
139
139
|
return {
|
|
140
140
|
character: position.character,
|
|
141
141
|
line: position.line,
|
|
@@ -143,13 +143,13 @@ function readLspPosition(position: ILineAndCharacter): ILspPosition {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
export class TsgoLspClient {
|
|
146
|
-
private readonly pendingRequests = new Map<number,
|
|
146
|
+
private readonly pendingRequests = new Map<number, PendingRequest>();
|
|
147
147
|
private readonly process: ReturnType<typeof spawn>;
|
|
148
148
|
private readonly stderrChunks: string[] = [];
|
|
149
149
|
private stdoutBuffer = Buffer.alloc(0);
|
|
150
150
|
private requestId = 0;
|
|
151
151
|
|
|
152
|
-
public constructor(private readonly options:
|
|
152
|
+
public constructor(private readonly options: TsgoLspClientOptions) {
|
|
153
153
|
this.process = spawn(this.options.tsgoExecutablePath, ["--lsp", "--stdio"], {
|
|
154
154
|
cwd: this.options.workspacePath,
|
|
155
155
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -187,7 +187,7 @@ export class TsgoLspClient {
|
|
|
187
187
|
|
|
188
188
|
public async initialize(): Promise<void> {
|
|
189
189
|
const rootUri = pathToFileURL(this.options.workspacePath).toString();
|
|
190
|
-
const initializeParams:
|
|
190
|
+
const initializeParams: LspInitializeParams = {
|
|
191
191
|
capabilities: {
|
|
192
192
|
textDocument: {
|
|
193
193
|
rename: {
|
|
@@ -214,8 +214,8 @@ export class TsgoLspClient {
|
|
|
214
214
|
this.sendNotification("initialized");
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
public prepareRename(filePath: string, position:
|
|
218
|
-
const params:
|
|
217
|
+
public prepareRename(filePath: string, position: LineAndCharacter): Promise<unknown> {
|
|
218
|
+
const params: LspPrepareRenameParams = {
|
|
219
219
|
position: readLspPosition(position),
|
|
220
220
|
textDocument: {
|
|
221
221
|
uri: pathToFileURL(filePath).toString(),
|
|
@@ -225,8 +225,8 @@ export class TsgoLspClient {
|
|
|
225
225
|
return this.sendRequest("textDocument/prepareRename", params);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
public rename(filePath: string, position:
|
|
229
|
-
const params:
|
|
228
|
+
public rename(filePath: string, position: LineAndCharacter, newName: string): Promise<unknown> {
|
|
229
|
+
const params: LspRenameParams = {
|
|
230
230
|
newName,
|
|
231
231
|
position: readLspPosition(position),
|
|
232
232
|
textDocument: {
|
|
@@ -289,7 +289,7 @@ export class TsgoLspClient {
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
private handleResponse(message:
|
|
292
|
+
private handleResponse(message: JsonRpcResponse): void {
|
|
293
293
|
if (typeof message.id !== "number") {
|
|
294
294
|
return;
|
|
295
295
|
}
|
|
@@ -309,7 +309,7 @@ export class TsgoLspClient {
|
|
|
309
309
|
pendingRequest.resolve(message.result ?? null);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
private handleServerRequest(message:
|
|
312
|
+
private handleServerRequest(message: JsonRpcRequestMessage): void {
|
|
313
313
|
if (message.id === undefined) {
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
@@ -401,7 +401,7 @@ export class TsgoLspClient {
|
|
|
401
401
|
clearTimeout(killTimer);
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
private writeMessage(method: string, id?:
|
|
404
|
+
private writeMessage(method: string, id?: JsonRpcIdentifier, params?: unknown): void {
|
|
405
405
|
const message: Record<string, unknown> = {
|
|
406
406
|
jsonrpc: "2.0",
|
|
407
407
|
method,
|
|
@@ -418,7 +418,7 @@ export class TsgoLspClient {
|
|
|
418
418
|
this.writeSerializedMessage(message);
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
private writeResponse(id:
|
|
421
|
+
private writeResponse(id: JsonRpcIdentifier, result: unknown): void {
|
|
422
422
|
this.writeSerializedMessage({
|
|
423
423
|
id,
|
|
424
424
|
jsonrpc: "2.0",
|
|
@@ -3,46 +3,46 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import { readMovedFileTextEdits } from "../../readMovedFileTextEdits.ts";
|
|
4
4
|
import { TsgoLspClient } from "./TsgoLspClient.ts";
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
ApplySemanticFixesOptions,
|
|
7
|
+
FileMove,
|
|
8
|
+
MoveFileOperation,
|
|
9
|
+
SemanticFixBackend,
|
|
10
|
+
SemanticFixBackendContext,
|
|
11
|
+
SemanticFixOperation,
|
|
12
|
+
SemanticFixPlan,
|
|
13
|
+
SemanticFixPlanResult,
|
|
14
|
+
SymbolRenameOperation,
|
|
15
|
+
TextEdit,
|
|
16
16
|
} from "../../types.ts";
|
|
17
17
|
|
|
18
|
-
type
|
|
18
|
+
type LspPosition = {
|
|
19
19
|
character: number;
|
|
20
20
|
line: number;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
type
|
|
24
|
-
end:
|
|
25
|
-
start:
|
|
23
|
+
type LspRange = {
|
|
24
|
+
end: LspPosition;
|
|
25
|
+
start: LspPosition;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
type
|
|
28
|
+
type LspTextEdit = {
|
|
29
29
|
newText: string;
|
|
30
|
-
range:
|
|
30
|
+
range: LspRange;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
type
|
|
33
|
+
type LspWorkspaceChanges = Record<string, readonly LspTextEdit[]>;
|
|
34
34
|
|
|
35
|
-
type
|
|
36
|
-
changes:
|
|
35
|
+
type LspWorkspaceEdit = {
|
|
36
|
+
changes: LspWorkspaceChanges;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
type
|
|
39
|
+
type ClientCache = Map<string, TsgoLspClient>;
|
|
40
40
|
|
|
41
41
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
42
42
|
return typeof value === "object" && value !== null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function isLspPosition(value: unknown): value is
|
|
45
|
+
function isLspPosition(value: unknown): value is LspPosition {
|
|
46
46
|
if (!isRecord(value)) {
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
@@ -50,7 +50,7 @@ function isLspPosition(value: unknown): value is ILspPosition {
|
|
|
50
50
|
return typeof value.line === "number" && typeof value.character === "number";
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function isLspRange(value: unknown): value is
|
|
53
|
+
function isLspRange(value: unknown): value is LspRange {
|
|
54
54
|
if (!isRecord(value)) {
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
@@ -58,7 +58,7 @@ function isLspRange(value: unknown): value is ILspRange {
|
|
|
58
58
|
return isLspPosition(value.start) && isLspPosition(value.end);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function isLspTextEdit(value: unknown): value is
|
|
61
|
+
function isLspTextEdit(value: unknown): value is LspTextEdit {
|
|
62
62
|
if (!isRecord(value)) {
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
@@ -66,7 +66,7 @@ function isLspTextEdit(value: unknown): value is ILspTextEdit {
|
|
|
66
66
|
return typeof value.newText === "string" && isLspRange(value.range);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function isLspWorkspaceEdit(value: unknown): value is
|
|
69
|
+
function isLspWorkspaceEdit(value: unknown): value is LspWorkspaceEdit {
|
|
70
70
|
if (!isRecord(value) || !isRecord(value.changes)) {
|
|
71
71
|
return false;
|
|
72
72
|
}
|
|
@@ -86,7 +86,7 @@ function isLspWorkspaceEdit(value: unknown): value is ILspWorkspaceEdit {
|
|
|
86
86
|
return true;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function compareTextEdit(left:
|
|
89
|
+
function compareTextEdit(left: TextEdit, right: TextEdit): number {
|
|
90
90
|
if (left.filePath !== right.filePath) {
|
|
91
91
|
return left.filePath.localeCompare(right.filePath);
|
|
92
92
|
}
|
|
@@ -102,7 +102,7 @@ function compareTextEdit(left: ITextEdit, right: ITextEdit): number {
|
|
|
102
102
|
return left.newText.localeCompare(right.newText);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function readTextEdit(uri: string, textEdit:
|
|
105
|
+
function readTextEdit(uri: string, textEdit: LspTextEdit): TextEdit {
|
|
106
106
|
return {
|
|
107
107
|
end: textEdit.range.end,
|
|
108
108
|
filePath: fileURLToPath(uri),
|
|
@@ -111,8 +111,8 @@ function readTextEdit(uri: string, textEdit: ILspTextEdit): ITextEdit {
|
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function readTextEdits(workspaceEdit:
|
|
115
|
-
const textEdits:
|
|
114
|
+
function readTextEdits(workspaceEdit: LspWorkspaceEdit): readonly TextEdit[] {
|
|
115
|
+
const textEdits: TextEdit[] = [];
|
|
116
116
|
|
|
117
117
|
for (const [uri, edits] of Object.entries(workspaceEdit.changes)) {
|
|
118
118
|
for (const textEdit of edits) {
|
|
@@ -132,9 +132,9 @@ function readFailureReason(error: unknown): string {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
async function readClient(
|
|
135
|
-
clientCache:
|
|
136
|
-
context:
|
|
137
|
-
options: Pick<
|
|
135
|
+
clientCache: ClientCache,
|
|
136
|
+
context: SemanticFixBackendContext,
|
|
137
|
+
options: Pick<ApplySemanticFixesOptions, "tsgoExecutablePath">,
|
|
138
138
|
): Promise<TsgoLspClient> {
|
|
139
139
|
const cachedClient = clientCache.get(context.targetDirectoryPath);
|
|
140
140
|
if (cachedClient) {
|
|
@@ -150,19 +150,19 @@ async function readClient(
|
|
|
150
150
|
return client;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function readRenameSymbolDescription(operation:
|
|
153
|
+
function readRenameSymbolDescription(operation: SymbolRenameOperation): string {
|
|
154
154
|
return `Rename ${operation.symbolName} to ${operation.newName}`;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
function readMoveFileDescription(operation:
|
|
157
|
+
function readMoveFileDescription(operation: MoveFileOperation): string {
|
|
158
158
|
return `Move ${operation.filePath} to ${operation.newFilePath}`;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
function readPlan(
|
|
162
|
-
operation:
|
|
163
|
-
textEdits: readonly
|
|
164
|
-
fileMoves: readonly
|
|
165
|
-
):
|
|
162
|
+
operation: SemanticFixOperation,
|
|
163
|
+
textEdits: readonly TextEdit[],
|
|
164
|
+
fileMoves: readonly FileMove[] = [],
|
|
165
|
+
): SemanticFixPlan {
|
|
166
166
|
return {
|
|
167
167
|
description:
|
|
168
168
|
operation.kind === "rename-symbol" ? readRenameSymbolDescription(operation) : readMoveFileDescription(operation),
|
|
@@ -174,12 +174,12 @@ function readPlan(
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
export function createTsgoLspSemanticFixBackend(
|
|
177
|
-
options: Pick<
|
|
178
|
-
):
|
|
179
|
-
const clientCache:
|
|
177
|
+
options: Pick<ApplySemanticFixesOptions, "tsgoExecutablePath">,
|
|
178
|
+
): SemanticFixBackend {
|
|
179
|
+
const clientCache: ClientCache = new Map();
|
|
180
180
|
|
|
181
181
|
return {
|
|
182
|
-
async createPlan(operation, context): Promise<
|
|
182
|
+
async createPlan(operation, context): Promise<SemanticFixPlanResult> {
|
|
183
183
|
switch (operation.kind) {
|
|
184
184
|
case "rename-symbol": {
|
|
185
185
|
const client = await readClient(clientCache, context, options);
|
|
@@ -1,71 +1,15 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, resolve } from "node:path";
|
|
3
1
|
import ts from "typescript";
|
|
4
2
|
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
OxlintDiagnostic,
|
|
4
|
+
SemanticFixOperation,
|
|
5
|
+
SemanticFixProvider,
|
|
6
|
+
SemanticFixProviderContext,
|
|
9
7
|
} from "../types.ts";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return resolve(context.targetDirectoryPath, diagnostic.filename);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function readInterfaceDeclarationAtOffset(node: ts.Node, offset: number): ts.InterfaceDeclaration | null {
|
|
20
|
-
if (ts.isInterfaceDeclaration(node)) {
|
|
21
|
-
const start = node.name.getStart();
|
|
22
|
-
const end = node.name.getEnd();
|
|
23
|
-
if (offset >= start && offset <= end) {
|
|
24
|
-
return node;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let matchingDeclaration: ts.InterfaceDeclaration | null = null;
|
|
29
|
-
|
|
30
|
-
ts.forEachChild(node, (childNode) => {
|
|
31
|
-
if (matchingDeclaration) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
matchingDeclaration = readInterfaceDeclarationAtOffset(childNode, offset);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
return matchingDeclaration;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function readOffsetFromLineAndColumn(sourceFile: ts.SourceFile, line: number, column: number): number | null {
|
|
42
|
-
if (line < 1 || column < 1) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
return ts.getPositionOfLineAndCharacter(sourceFile, line - 1, column - 1);
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function readInterfaceDeclarationFromLabel(
|
|
54
|
-
sourceFile: ts.SourceFile,
|
|
55
|
-
label: IOxlintDiagnostic["labels"][number],
|
|
56
|
-
): ts.InterfaceDeclaration | null {
|
|
57
|
-
const declarationAtReportedOffset = readInterfaceDeclarationAtOffset(sourceFile, label.span.offset);
|
|
58
|
-
if (declarationAtReportedOffset) {
|
|
59
|
-
return declarationAtReportedOffset;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const offsetFromLineAndColumn = readOffsetFromLineAndColumn(sourceFile, label.span.line, label.span.column);
|
|
63
|
-
if (offsetFromLineAndColumn === null || offsetFromLineAndColumn === label.span.offset) {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return readInterfaceDeclarationAtOffset(sourceFile, offsetFromLineAndColumn);
|
|
68
|
-
}
|
|
8
|
+
import {
|
|
9
|
+
readDiagnosticSourceFile,
|
|
10
|
+
readNamedDeclarationFromDiagnosticLabel,
|
|
11
|
+
readRenameSymbolOperation,
|
|
12
|
+
} from "./helpers.ts";
|
|
69
13
|
|
|
70
14
|
function readNormalizedInterfaceName(interfaceName: string): string | null {
|
|
71
15
|
const rawBaseName = /^[Ii]/.test(interfaceName) ? interfaceName.slice(1) : interfaceName;
|
|
@@ -83,19 +27,14 @@ function readNormalizedInterfaceName(interfaceName: string): string | null {
|
|
|
83
27
|
return normalizedInterfaceName;
|
|
84
28
|
}
|
|
85
29
|
|
|
86
|
-
function readOperation(
|
|
87
|
-
diagnostic: IOxlintDiagnostic,
|
|
88
|
-
context: ISemanticFixProviderContext,
|
|
89
|
-
): ISemanticFixOperation | null {
|
|
30
|
+
function readOperation(diagnostic: OxlintDiagnostic, context: SemanticFixProviderContext): SemanticFixOperation | null {
|
|
90
31
|
const label = diagnostic.labels[0];
|
|
91
32
|
if (!label) {
|
|
92
33
|
return null;
|
|
93
34
|
}
|
|
94
35
|
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
98
|
-
const interfaceDeclaration = readInterfaceDeclarationFromLabel(sourceFile, label);
|
|
36
|
+
const sourceFile = readDiagnosticSourceFile(diagnostic, context);
|
|
37
|
+
const interfaceDeclaration = readNamedDeclarationFromDiagnosticLabel(sourceFile, label, ts.isInterfaceDeclaration);
|
|
99
38
|
if (!interfaceDeclaration) {
|
|
100
39
|
return null;
|
|
101
40
|
}
|
|
@@ -106,25 +45,12 @@ function readOperation(
|
|
|
106
45
|
return null;
|
|
107
46
|
}
|
|
108
47
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
filePath,
|
|
113
|
-
id: `${diagnostic.code}:${filePath}:${start.line}:${start.character}:${newName}`,
|
|
114
|
-
kind: "rename-symbol",
|
|
115
|
-
newName,
|
|
116
|
-
position: {
|
|
117
|
-
character: start.character,
|
|
118
|
-
line: start.line,
|
|
119
|
-
},
|
|
120
|
-
ruleCode: diagnostic.code,
|
|
121
|
-
symbolName,
|
|
122
|
-
};
|
|
48
|
+
return readRenameSymbolOperation(diagnostic, sourceFile, interfaceDeclaration.name, newName);
|
|
123
49
|
}
|
|
124
50
|
|
|
125
|
-
export function createInterfaceNamingConventionSemanticFixProvider():
|
|
51
|
+
export function createInterfaceNamingConventionSemanticFixProvider(): SemanticFixProvider {
|
|
126
52
|
return {
|
|
127
|
-
createOperation(diagnostic, context):
|
|
53
|
+
createOperation(diagnostic, context): SemanticFixOperation | null {
|
|
128
54
|
return readOperation(diagnostic, context);
|
|
129
55
|
},
|
|
130
56
|
ruleCode: "@alexgorbatchev/interface-naming-convention",
|