Skip to content

Commit 5e3cbb0

Browse files
committed
arch: add code action architecture & example which is disable at the moment
1 parent 429c48b commit 5e3cbb0

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { compact } from '@zardoy/utils'
2+
import type tslib from 'typescript/lib/tsserverlibrary'
3+
import toggleBraces from './toggleBraces'
4+
5+
type SimplifiedRefactorInfo = {
6+
start: number
7+
length: number
8+
newText: string
9+
}
10+
11+
export type ApplyCodeAction = (
12+
ts: typeof tslib,
13+
sourceFile: tslib.SourceFile,
14+
position: number,
15+
range?: tslib.TextRange,
16+
) => tslib.RefactorEditInfo | SimplifiedRefactorInfo[] | undefined
17+
18+
export type CodeAction = {
19+
name: string
20+
id: string
21+
tryToApply: ApplyCodeAction
22+
}
23+
24+
const codeActions: CodeAction[] = [
25+
/* toggleBraces */
26+
]
27+
28+
export const REFACTORS_CATEGORY = 'essential-refactors'
29+
30+
export default (
31+
ts: typeof tslib,
32+
sourceFile: tslib.SourceFile,
33+
positionOrRange: tslib.TextRange | number,
34+
requestingEditsId?: string,
35+
): { info?: ts.ApplicableRefactorInfo; edit: tslib.RefactorEditInfo } => {
36+
const range = typeof positionOrRange !== 'number' && positionOrRange.pos !== positionOrRange.end ? positionOrRange : undefined
37+
const appliableCodeActions = compact(
38+
codeActions.map(action => {
39+
const edits = action.tryToApply(ts, sourceFile, typeof positionOrRange === 'number' ? positionOrRange : positionOrRange.pos, range)
40+
if (!edits) return
41+
return {
42+
...action,
43+
edits: Array.isArray(edits)
44+
? ({
45+
edits: [
46+
{
47+
fileName: sourceFile.fileName,
48+
textChanges: edits.map(({ length, newText, start }) => {
49+
return {
50+
newText,
51+
span: {
52+
length,
53+
start,
54+
},
55+
}
56+
}),
57+
},
58+
],
59+
} as tslib.RefactorEditInfo)
60+
: edits,
61+
}
62+
}),
63+
)
64+
65+
return {
66+
info:
67+
(appliableCodeActions.length && {
68+
actions: appliableCodeActions.map(({ id, name }) => ({
69+
description: name,
70+
name: id,
71+
})),
72+
// anyway not visible in ui
73+
description: 'Essential Refactors',
74+
name: REFACTORS_CATEGORY,
75+
}) ||
76+
undefined,
77+
edit: requestingEditsId ? appliableCodeActions.find(({ id }) => id === requestingEditsId)!.edits : null!,
78+
}
79+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Statement } from 'typescript/lib/tsserverlibrary'
2+
import { findChildContainingPosition, findClosestParent, getIndentFromPos } from '../utils'
3+
import { ApplyCodeAction, CodeAction } from './getCodeActions'
4+
5+
const tryToApply: ApplyCodeAction = (ts, sourceFile, pos, range) => {
6+
const currentNode = findChildContainingPosition(ts, sourceFile, pos)
7+
if (!currentNode) return
8+
const closestBlock = findClosestParent(
9+
ts,
10+
currentNode,
11+
[
12+
ts.SyntaxKind.IfStatement,
13+
ts.SyntaxKind.ForStatement,
14+
ts.SyntaxKind.ForOfStatement,
15+
ts.SyntaxKind.ForInStatement,
16+
ts.SyntaxKind.WhileStatement,
17+
// ts.SyntaxKind.Block
18+
],
19+
[],
20+
)
21+
if (!closestBlock) return
22+
let wrapNode: Statement | undefined
23+
if (ts.isForStatement(closestBlock) || ts.isForOfStatement(closestBlock) || ts.isForInStatement(closestBlock) || ts.isWhileStatement(closestBlock)) {
24+
wrapNode = closestBlock.statement
25+
} else if (ts.isIfStatement(closestBlock)) {
26+
wrapNode = closestBlock.thenStatement
27+
}
28+
if (wrapNode && !ts.isBlock(wrapNode)) {
29+
const startIndent = getIndentFromPos(ts, sourceFile, pos)
30+
return [
31+
{ start: wrapNode.getStart(), length: 0, newText: `{\n${startIndent}\t` },
32+
{ start: wrapNode.getEnd(), length: 0, newText: `\n${startIndent}}` },
33+
]
34+
}
35+
return
36+
}
37+
38+
export default {
39+
name: 'Toggle Braces',
40+
id: 'toggleBraces',
41+
tryToApply,
42+
} as CodeAction

typescript/src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { findChildContainingPosition, getIndentFromPos } from './utils'
1313
import { getParameterListParts } from './snippetForFunctionCall'
1414
import { getNavTreeItems } from './getPatchedNavTree'
1515
import { join } from 'path'
16+
import getCodeActions, { REFACTORS_CATEGORY } from './codeActions/getCodeActions'
1617

1718
const thisPluginMarker = Symbol('__essentialPluginsMarker__')
1819

@@ -154,9 +155,25 @@ export = function ({ typescript }: { typescript: typeof import('typescript/lib/t
154155

155156
if (c('markTsCodeActions.enable')) prior = prior.map(item => ({ ...item, description: `🔵 ${item.description}` }))
156157

158+
const program = info.languageService.getProgram()
159+
const sourceFile = program!.getSourceFile(fileName)!
160+
const { info: refactorInfo } = getCodeActions(ts, sourceFile, positionOrRange)
161+
if (refactorInfo) prior = [...prior, refactorInfo]
162+
157163
return prior
158164
}
159165

166+
proxy.getEditsForRefactor = (fileName, formatOptions, positionOrRange, refactorName, actionName, preferences) => {
167+
const category = refactorName
168+
if (category === REFACTORS_CATEGORY) {
169+
const program = info.languageService.getProgram()
170+
const sourceFile = program!.getSourceFile(fileName)!
171+
const { edit } = getCodeActions(ts, sourceFile, positionOrRange, actionName)
172+
return edit
173+
}
174+
return info.languageService.getEditsForRefactor(fileName, formatOptions, positionOrRange, refactorName, actionName, preferences)
175+
}
176+
160177
proxy.getCodeFixesAtPosition = (fileName, start, end, errorCodes, formatOptions, preferences) => {
161178
let prior = info.languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences)
162179
// fix builtin codefixes/refactorings

0 commit comments

Comments
 (0)