Skip to content

Commit b89da4b

Browse files
committed
feat: introducing new completions for @default JSDoc !!!
also tried to implement go to type & auto-editing for default, but dk how :
1 parent bbf1738 commit b89da4b

File tree

4 files changed

+88
-5
lines changed

4 files changed

+88
-5
lines changed

src/onCompletionAccepted.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ export default (tsApi: { onCompletionAccepted }) => {
1616
}
1717

1818
const { insertText, documentation = '', kind } = item
19-
if (kind === vscode.CompletionItemKind.Keyword && insertText === 'return ') {
20-
justAcceptedReturnKeywordSuggestion = true
19+
if (kind === vscode.CompletionItemKind.Keyword) {
20+
if (insertText === 'return ') justAcceptedReturnKeywordSuggestion = true
21+
else if (insertText === 'default ') void vscode.commands.executeCommand('editor.action.triggerSuggest')
2122
}
2223

2324
const enableMethodSnippets = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, item.document).get('enableMethodSnippets')
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { findChildContainingPositionIncludingStartTrivia } from '../utils'
2+
3+
export const getJsdocDefaultTypes = (position: number, sourceFile: ts.SourceFile, languageService: ts.LanguageService) => {
4+
const fileText = sourceFile.getFullText().slice(0, position)
5+
const textBeforeWord = fileText.slice(0, /[-\w\d]*$/i.exec(fileText)!.index)
6+
if (!textBeforeWord.endsWith('@default ')) return
7+
const comment = languageService.getSpanOfEnclosingComment(sourceFile.fileName, position, false)
8+
if (!comment) return
9+
let node = findChildContainingPositionIncludingStartTrivia(ts, sourceFile, position)
10+
if (!node) return
11+
if (ts.isVariableDeclarationList(node)) node = node.declarations[0]
12+
if (!node) return
13+
const typeChecker = languageService.getProgram()!.getTypeChecker()!
14+
try {
15+
const type = typeChecker.getTypeAtLocation(node)
16+
if (!type.isUnion()) return
17+
const suggestions: [name: string, type: ts.Type, isLiteral: boolean][] = []
18+
for (const nextType of type.types) {
19+
const addSuggestions = (isLiteral: boolean, ...addSuggestions: string[]) => {
20+
suggestions.push(...addSuggestions.map(suggestion => [suggestion, nextType, isLiteral] as [string, ts.Type, boolean]))
21+
}
22+
if (nextType.isLiteral()) addSuggestions(true, nextType.value.toString())
23+
else if (nextType.flags & ts.TypeFlags.BooleanLiteral) addSuggestions(true, nextType['intrinsicName'])
24+
else if (nextType.flags & ts.TypeFlags.Boolean) addSuggestions(false, 'true', 'false')
25+
else if (nextType.flags & ts.TypeFlags.Undefined) addSuggestions(false, 'undefined')
26+
else if (nextType.flags & ts.TypeFlags.Null) addSuggestions(false, 'null')
27+
}
28+
return suggestions
29+
} catch (err) {
30+
return
31+
}
32+
33+
return
34+
}
35+
36+
export default (
37+
entries: ts.CompletionEntry[],
38+
position: number,
39+
sourceFile: ts.SourceFile,
40+
languageService: ts.LanguageService,
41+
): ts.CompletionEntry[] | undefined => {
42+
const suggestions = getJsdocDefaultTypes(position, sourceFile, languageService)
43+
if (!suggestions) return
44+
return [
45+
...[...new Set(suggestions)].map(
46+
([s]): ts.CompletionEntry => ({
47+
name: s,
48+
sortText: '07',
49+
kind: ts.ScriptElementKind.string,
50+
}),
51+
),
52+
...entries,
53+
] as ts.CompletionEntry[]
54+
}

typescript/src/completionsAtPosition.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import additionalTypesSuggestions from './completions/additionalTypesSuggestions
1616
import boostKeywordSuggestions from './completions/boostKeywordSuggestions'
1717
import boostTextSuggestions from './completions/boostNameSuggestions'
1818
import keywordsSpace from './completions/keywordsSpace'
19+
import jsdocDefault from './completions/jsdocDefault'
1920

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

@@ -166,11 +167,12 @@ export const getCompletionsAtPosition = (
166167
if (leftNode && c('switchExcludeCoveredCases')) prior.entries = switchCaseExcludeCovered(prior.entries, position, sourceFile, leftNode) ?? prior.entries
167168

168169
prior.entries = arrayMethods(prior.entries, position, sourceFile, c) ?? prior.entries
170+
prior.entries = jsdocDefault(prior.entries, position, sourceFile, languageService) ?? prior.entries
169171

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

172174
for (const rule of c('replaceSuggestions')) {
173-
let foundIndex: number
175+
let foundIndex!: number
174176
const suggestion = prior.entries.find(({ name, kind }, index) => {
175177
if (rule.suggestion !== name) return false
176178
if (rule.filter?.kind && kind !== rule.filter.kind) return false
@@ -179,9 +181,9 @@ export const getCompletionsAtPosition = (
179181
})
180182
if (!suggestion) continue
181183

182-
if (rule.delete) prior.entries.splice(foundIndex!, 1)
184+
if (rule.delete) prior.entries.splice(foundIndex, 1)
183185

184-
if (rule.duplicateOriginal) prior.entries.splice(rule.duplicateOriginal === 'above' ? foundIndex! : foundIndex! + 1, 0, { ...suggestion })
186+
if (rule.duplicateOriginal) prior.entries.splice(rule.duplicateOriginal === 'above' ? foundIndex : foundIndex + 1, 0, { ...suggestion })
185187

186188
Object.assign(suggestion, rule.patch ?? {})
187189
if (rule.patch?.insertText) suggestion.isSnippet = true

typescript/src/utils.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ export function findChildContainingPosition(typescript: typeof ts, sourceFile: t
1111
return find(sourceFile)
1212
}
1313

14+
export function findChildContainingPositionIncludingStartTrivia(typescript: typeof ts, sourceFile: ts.SourceFile, position: number): ts.Node | undefined {
15+
function find(node: ts.Node): ts.Node | undefined {
16+
if (position >= node.getStart() - (node.getLeadingTriviaWidth() ?? 0) && position < node.getEnd()) {
17+
return typescript.forEachChild(node, find) || node
18+
}
19+
20+
return
21+
}
22+
return find(sourceFile)
23+
}
24+
1425
export function findChildContainingExactPosition(sourceFile: ts.SourceFile, position: number): ts.Node | undefined {
1526
function find(node: ts.Node): ts.Node | undefined {
1627
if (position >= node.getStart() && position <= node.getEnd()) {
@@ -143,3 +154,18 @@ export function addObjectMethodResultInterceptors<T extends Record<string, any>>
143154
}
144155
}
145156
}
157+
158+
const wordRangeAtPos = (text: string, position: number) => {
159+
const isGood = (pos: number) => {
160+
return /[-\w\d]/i.test(text.at(pos) ?? '')
161+
}
162+
let startPos = position
163+
while (isGood(startPos)) {
164+
startPos--
165+
}
166+
let endPos = position
167+
while (isGood(endPos)) {
168+
endPos++
169+
}
170+
return text.slice(startPos + 1, endPos)
171+
}

0 commit comments

Comments
 (0)