Skip to content

Commit 630fdcf

Browse files
committed
fix microsoft/TypeScript#13711 for 99% cases
1 parent c637b46 commit 630fdcf

File tree

5 files changed

+94
-0
lines changed

5 files changed

+94
-0
lines changed

src/configurationType.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ export type Configuration = {
234234
* @default false
235235
*/
236236
patchOutline: boolean
237+
/**
238+
* Exclude covered strings/enum cases in switch
239+
* @default true
240+
*/
241+
switchExcludeCoveredCases: boolean
237242
/**
238243
* Improve JSX attribute completions:
239244
* - enable builtin jsx attribute completion fix
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { oneOf } from '@zardoy/utils'
2+
import { cleanupEntryName } from '../utils'
3+
4+
// implementation not even ideal, but it just works for string & enums, which are used in 99% cases
5+
export default (entries: ts.CompletionEntry[], position: number, sourceFile: ts.SourceFile, leftNode: ts.Node) => {
6+
let nodeComp = leftNode
7+
let enumAccessExpr: string | null | undefined
8+
if (ts.isStringLiteral(leftNode)) enumAccessExpr = null
9+
else {
10+
enumAccessExpr = getPropAccessExprRestText(leftNode)
11+
if (!ts.isCaseClause(nodeComp.parent)) nodeComp = leftNode.parent
12+
}
13+
if (enumAccessExpr === undefined) return
14+
let currentClause: ts.CaseClause
15+
// just for type inferrence
16+
const clauses = ts.isCaseClause(nodeComp.parent) && ts.isCaseBlock(nodeComp.parent.parent) ? nodeComp.parent.parent?.clauses : undefined
17+
if (!clauses) return
18+
currentClause = nodeComp.parent as ts.CaseClause
19+
const coveredValues: string[] = []
20+
for (const clause of clauses) {
21+
if (ts.isDefaultClause(clause) || clause === currentClause) continue
22+
const { expression } = clause
23+
if (enumAccessExpr === null) {
24+
if (ts.isStringLiteralLike(expression)) coveredValues.push(expression.text)
25+
} else {
26+
if (getPropAccessExprRestText(expression) === enumAccessExpr) {
27+
coveredValues.push((expression as ts.PropertyAccessExpression).name.text)
28+
}
29+
}
30+
}
31+
return entries.filter(
32+
({ name, kind }) =>
33+
!oneOf(kind, ts.ScriptElementKind.memberVariableElement, ts.ScriptElementKind.enumMemberElement, ts.ScriptElementKind.string) ||
34+
!coveredValues.includes(cleanupEntryName({ name })),
35+
)
36+
}
37+
38+
const getPropAccessExprRestText = (node: ts.Node) => {
39+
let propNode = node
40+
if (ts.isPropertyAccessExpression(node.parent)) {
41+
propNode = node.parent
42+
}
43+
if (!ts.isPropertyAccessExpression(propNode)) return
44+
return propNode.getText().slice(0, propNode.name.getStart() - propNode.getStart() - 1 /* -1 for dot */)
45+
}

typescript/src/completionsAtPosition.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import improveJsxCompletions from './completions/jsxAttributes'
1111
import arrayMethods from './completions/arrayMethods'
1212
import prepareTextForEmmet from './specialCommands/prepareTextForEmmet'
1313
import objectLiteralHelpers from './completions/objectLiteralHelpers'
14+
import switchCaseExcludeCovered from './completions/switchCaseExcludeCovered'
1415

1516
export type PrevCompletionMap = Record<string, { originalName?: string; documentationOverride?: string | ts.SymbolDisplayPart[] }>
1617

@@ -176,6 +177,8 @@ export const getCompletionsAtPosition = (
176177
})
177178
}
178179

180+
if (leftNode && c('switchExcludeCoveredCases')) prior.entries = switchCaseExcludeCovered(prior.entries, position, sourceFile, leftNode) ?? prior.entries
181+
179182
prior.entries = arrayMethods(prior.entries, position, sourceFile, c) ?? prior.entries
180183

181184
if (c('improveJsxCompletions') && leftNode) prior.entries = improveJsxCompletions(prior.entries, leftNode, position, sourceFile, c('jsxCompletionsMap'))

typescript/src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ export const getLineTextBeforePos = (sourceFile: ts.SourceFile, position: number
5858
return sourceFile.getFullText().slice(position - character, position)
5959
}
6060

61+
export const cleanupEntryName = ({ name }: Pick<ts.CompletionEntry, 'name'>) => {
62+
// intellicode highlighting
63+
return name.replace(/^ /, '')
64+
}
65+
6166
// Workaround esbuild bundle modules
6267
export const nodeModules = __WEB__
6368
? null

typescript/test/completions.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,42 @@ test('Array Method Snippets', () => {
219219
}
220220
})
221221

222+
test('Switch Case Exclude Covered', () => {
223+
const [, _, numPositions] = fileContentsSpecialPositions(/*ts*/ `
224+
let test: 'foo' | 'bar'
225+
switch (test) {
226+
case 'foo':
227+
break;
228+
case '/*|*/':
229+
break;
230+
default:
231+
break;
232+
}
233+
234+
enum SomeEnum {
235+
A,
236+
B
237+
}
238+
let test2: SomeEnum
239+
switch (test2) {
240+
case SomeEnum.B:
241+
break;
242+
case SomeEnum./*|*/:
243+
break;
244+
default:
245+
break;
246+
}
247+
`)
248+
const completionsByPos = {
249+
1: ['bar'],
250+
2: ['A'],
251+
}
252+
for (const [i, pos] of Object.entries(numPositions)) {
253+
const { entryNames } = getCompletionsAtPosition(pos as number) ?? {}
254+
expect(entryNames).toEqual(completionsByPos[i])
255+
}
256+
})
257+
222258
// TODO move/remove this test from here
223259
test('Patched navtree (outline)', () => {
224260
globalThis.__TS_SEVER_PATH__ = require.resolve('typescript/lib/tsserver')

0 commit comments

Comments
 (0)