|
| 1 | +import { boostExistingSuggestions, boostOrAddSuggestions, findChildContainingPosition } from '../utils' |
| 2 | + |
| 3 | +// 1. add suggestions for unresolved indentifiers in code |
| 4 | +// 2. boost identifer or type name suggestion |
| 5 | +export default ( |
| 6 | + entries: ts.CompletionEntry[], |
| 7 | + position: number, |
| 8 | + sourceFile: ts.SourceFile, |
| 9 | + node: ts.Node, |
| 10 | + languageService: ts.LanguageService, |
| 11 | +): ts.CompletionEntry[] | undefined => { |
| 12 | + // todo getPreviousPartNode() util |
| 13 | + // todo object key |
| 14 | + const fileText = sourceFile.getFullText() |
| 15 | + const fileTextBeforePos = fileText.slice(0, position) |
| 16 | + const preConstNodeOffset = fileTextBeforePos.match(/(?:const|let) ([\w\d]*)$/i)?.[1] |
| 17 | + /** false - pick all identifiers after cursor |
| 18 | + * node - pick identifiers that within node */ |
| 19 | + let filterBlock: undefined | false | ts.Node |
| 20 | + if (preConstNodeOffset !== undefined) { |
| 21 | + const node = findChildContainingPosition(ts, sourceFile, position - preConstNodeOffset.length - 2) |
| 22 | + if (!node || !ts.isVariableDeclarationList(node)) return |
| 23 | + filterBlock = false |
| 24 | + } else if (ts.isIdentifier(node) && node.parent?.parent) { |
| 25 | + // node > parent1 > parent2 |
| 26 | + let parent1 = node.parent |
| 27 | + let parent2 = parent1.parent |
| 28 | + if (ts.isParameter(parent1) && isFunction(parent2)) { |
| 29 | + filterBlock = parent2.body ?? false |
| 30 | + } |
| 31 | + if (ts.isQualifiedName(parent1)) parent1 = parent1.parent |
| 32 | + parent2 = parent1.parent |
| 33 | + if (ts.isTypeReferenceNode(parent1) && ts.isParameter(parent2) && isFunction(parent2.parent) && ts.isIdentifier(parent2.name)) { |
| 34 | + const name = parent2.name.text.replace(/^_/, '') |
| 35 | + // its name convention in TS |
| 36 | + const nameUpperFirst = name[0]!.toUpperCase() + name.slice(1) |
| 37 | + return boostExistingSuggestions(entries, ({ name }) => { |
| 38 | + if (!name.includes(nameUpperFirst)) return false |
| 39 | + return true |
| 40 | + }) |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + if (filterBlock === undefined) return |
| 45 | + const semanticDiagnostics = languageService.getSemanticDiagnostics(sourceFile.fileName) |
| 46 | + |
| 47 | + const notFoundIdentifiers = semanticDiagnostics |
| 48 | + .filter(({ code }) => [2552, 2304].includes(code)) |
| 49 | + .filter(({ start, length }) => { |
| 50 | + if ([start, length].some(x => x === undefined)) return false |
| 51 | + if (filterBlock === false) return start! > position |
| 52 | + const diagnosticEnd = start! + length! |
| 53 | + const { pos, end } = filterBlock! |
| 54 | + if (start! < pos) return false |
| 55 | + if (diagnosticEnd > end) return false |
| 56 | + return true |
| 57 | + }) |
| 58 | + const generalNotFoundNames = [...new Set(notFoundIdentifiers.map(({ start, length }) => fileText.slice(start!, start! + length!)))] |
| 59 | + return boostOrAddSuggestions( |
| 60 | + entries, |
| 61 | + generalNotFoundNames.map(name => ({ name, kind: ts.ScriptElementKind.warning })), |
| 62 | + ) |
| 63 | + |
| 64 | + function isFunction(node: ts.Node): node is ts.ArrowFunction | ts.FunctionDeclaration { |
| 65 | + if (!node) return false |
| 66 | + return ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) |
| 67 | + } |
| 68 | +} |
0 commit comments