1namespace ts { 2 /** The version of the language service API */ 3 export const servicesVersion = "0.8"; 4 5 function createNode<TKind extends SyntaxKind>(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject<TKind> | IdentifierObject | PrivateIdentifierObject { 6 const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) : 7 kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) : 8 kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) : 9 new TokenObject(kind, pos, end); 10 node.parent = parent; 11 node.flags = parent.flags & NodeFlags.ContextFlags; 12 return node; 13 } 14 15 class NodeObject implements Node { 16 public kind: SyntaxKind; 17 public pos: number; 18 public end: number; 19 public flags: NodeFlags; 20 public modifierFlagsCache: ModifierFlags; 21 public transformFlags: TransformFlags; 22 public parent: Node; 23 public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined 24 public jsDoc?: JSDoc[]; 25 public original?: Node; 26 private _children: Node[] | undefined; 27 28 constructor(kind: SyntaxKind, pos: number, end: number) { 29 this.pos = pos; 30 this.end = end; 31 this.flags = NodeFlags.None; 32 this.modifierFlagsCache = ModifierFlags.None; 33 this.transformFlags = TransformFlags.None; 34 this.parent = undefined!; 35 this.kind = kind; 36 } 37 38 private assertHasRealPosition(message?: string) { 39 // eslint-disable-next-line local/debug-assert 40 Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation"); 41 } 42 43 public getSourceFile(): SourceFile { 44 return getSourceFileOfNode(this); 45 } 46 47 public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { 48 this.assertHasRealPosition(); 49 return getTokenPosOfNode(this, sourceFile, includeJsDocComment); 50 } 51 52 public getFullStart(): number { 53 this.assertHasRealPosition(); 54 return this.pos; 55 } 56 57 public getEnd(): number { 58 this.assertHasRealPosition(); 59 return this.end; 60 } 61 62 public getWidth(sourceFile?: SourceFile): number { 63 this.assertHasRealPosition(); 64 return this.getEnd() - this.getStart(sourceFile); 65 } 66 67 public getFullWidth(): number { 68 this.assertHasRealPosition(); 69 return this.end - this.pos; 70 } 71 72 public getLeadingTriviaWidth(sourceFile?: SourceFile): number { 73 this.assertHasRealPosition(); 74 return this.getStart(sourceFile) - this.pos; 75 } 76 77 public getFullText(sourceFile?: SourceFile): string { 78 this.assertHasRealPosition(); 79 return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); 80 } 81 82 public getText(sourceFile?: SourceFile): string { 83 this.assertHasRealPosition(); 84 if (!sourceFile) { 85 sourceFile = this.getSourceFile(); 86 } 87 return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); 88 } 89 90 public getChildCount(sourceFile?: SourceFile): number { 91 return this.getChildren(sourceFile).length; 92 } 93 94 public getChildAt(index: number, sourceFile?: SourceFile): Node { 95 return this.getChildren(sourceFile)[index]; 96 } 97 98 public getChildren(sourceFile?: SourceFileLike): Node[] { 99 this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine"); 100 return this._children || (this._children = createChildren(this, sourceFile)); 101 } 102 103 public getFirstToken(sourceFile?: SourceFileLike): Node | undefined { 104 this.assertHasRealPosition(); 105 const children = this.getChildren(sourceFile); 106 if (!children.length) { 107 return undefined; 108 } 109 110 const child = find(children, kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode)!; 111 return child.kind < SyntaxKind.FirstNode ? 112 child : 113 child.getFirstToken(sourceFile); 114 } 115 116 public getLastToken(sourceFile?: SourceFileLike): Node | undefined { 117 this.assertHasRealPosition(); 118 const children = this.getChildren(sourceFile); 119 120 const child = lastOrUndefined(children); 121 if (!child) { 122 return undefined; 123 } 124 125 return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); 126 } 127 128 public forEachChild<T>(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray<Node>) => T): T | undefined { 129 return forEachChild(this, cbNode, cbNodeArray); 130 } 131 } 132 133 function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] { 134 if (!isNodeKind(node.kind)) { 135 return emptyArray; 136 } 137 138 const children: Node[] = []; 139 140 if (isJSDocCommentContainingNode(node)) { 141 /** Don't add trivia for "tokens" since this is in a comment. */ 142 node.forEachChild(child => { 143 children.push(child); 144 }); 145 return children; 146 } 147 148 const sourceFileName: string | undefined = sourceFile ? sourceFile.fileName : node.getSourceFile().fileName; 149 if (sourceFileName && getScriptKindFromFileName(sourceFileName) === ScriptKind.ETS) { 150 scanner.setEtsContext(true); 151 } 152 153 scanner.setText((sourceFile || node.getSourceFile()).text); 154 let pos = node.pos; 155 const processNode = (child: Node) => { 156 addSyntheticNodes(children, pos, child.pos, node); 157 children.push(child); 158 pos = child.end; 159 }; 160 const processNodes = (nodes: NodeArray<Node>) => { 161 addSyntheticNodes(children, pos, nodes.pos, node); 162 children.push(createSyntaxList(nodes, node)); 163 pos = nodes.end; 164 }; 165 // jsDocComments need to be the first children 166 forEach((node as JSDocContainer).jsDoc, processNode); 167 // For syntactic classifications, all trivia are classified together, including jsdoc comments. 168 // For that to work, the jsdoc comments should still be the leading trivia of the first child. 169 // Restoring the scanner position ensures that. 170 pos = node.pos; 171 node.forEachChild(processNode, processNodes); 172 addSyntheticNodes(children, pos, node.end, node); 173 scanner.setText(undefined); 174 scanner.setEtsContext(false); 175 return children; 176 } 177 178 function addSyntheticNodes(nodes: Push<Node>, pos: number, end: number, parent: Node): void { 179 // position start === end mean the node is virtual 180 if(parent.virtual){ 181 return; 182 } 183 scanner.setTextPos(pos); 184 while (pos < end) { 185 const token = scanner.scan(); 186 const textPos = scanner.getTextPos(); 187 if (textPos <= end) { 188 if (token === SyntaxKind.Identifier) { 189 Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`); 190 } 191 nodes.push(createNode(token, pos, textPos, parent)); 192 } 193 pos = textPos; 194 if (token === SyntaxKind.EndOfFileToken) { 195 break; 196 } 197 } 198 } 199 200 function createSyntaxList(nodes: NodeArray<Node>, parent: Node): Node { 201 const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList; 202 list._children = []; 203 let pos = nodes.pos; 204 for (const node of nodes) { 205 // position start === end mean the node is visual 206 if (node.virtual) { 207 continue; 208 } 209 addSyntheticNodes(list._children, pos, node.pos, parent); 210 list._children.push(node); 211 pos = node.end; 212 } 213 addSyntheticNodes(list._children, pos, nodes.end, parent); 214 return list; 215 } 216 217 class TokenOrIdentifierObject implements Node { 218 public kind!: SyntaxKind; 219 public pos: number; 220 public end: number; 221 public flags: NodeFlags; 222 public modifierFlagsCache: ModifierFlags; 223 public transformFlags: TransformFlags; 224 public parent: Node; 225 public symbol!: Symbol; 226 public jsDocComments?: JSDoc[]; 227 228 constructor(pos: number, end: number) { 229 // Set properties in same order as NodeObject 230 this.pos = pos; 231 this.end = end; 232 this.flags = NodeFlags.None; 233 this.modifierFlagsCache = ModifierFlags.None; 234 this.transformFlags = TransformFlags.None; 235 this.parent = undefined!; 236 } 237 238 public getSourceFile(): SourceFile { 239 return getSourceFileOfNode(this); 240 } 241 242 public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { 243 return getTokenPosOfNode(this, sourceFile, includeJsDocComment); 244 } 245 246 public getFullStart(): number { 247 return this.pos; 248 } 249 250 public getEnd(): number { 251 return this.end; 252 } 253 254 public getWidth(sourceFile?: SourceFile): number { 255 return this.getEnd() - this.getStart(sourceFile); 256 } 257 258 public getFullWidth(): number { 259 return this.end - this.pos; 260 } 261 262 public getLeadingTriviaWidth(sourceFile?: SourceFile): number { 263 return this.getStart(sourceFile) - this.pos; 264 } 265 266 public getFullText(sourceFile?: SourceFile): string { 267 return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); 268 } 269 270 public getText(sourceFile?: SourceFile): string { 271 if (!sourceFile) { 272 sourceFile = this.getSourceFile(); 273 } 274 return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); 275 } 276 277 public getChildCount(): number { 278 return this.getChildren().length; 279 } 280 281 public getChildAt(index: number): Node { 282 return this.getChildren()[index]; 283 } 284 285 public getChildren(): Node[] { 286 return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray; 287 } 288 289 public getFirstToken(): Node | undefined { 290 return undefined; 291 } 292 293 public getLastToken(): Node | undefined { 294 return undefined; 295 } 296 297 public forEachChild<T>(): T | undefined { 298 return undefined; 299 } 300 } 301 302 class SymbolObject implements Symbol { 303 flags: SymbolFlags; 304 escapedName: __String; 305 declarations!: Declaration[]; 306 valueDeclaration!: Declaration; 307 308 // Undefined is used to indicate the value has not been computed. If, after computing, the 309 // symbol has no doc comment, then the empty array will be returned. 310 documentationComment?: SymbolDisplayPart[]; 311 tags?: JSDocTagInfo[]; // same 312 313 contextualGetAccessorDocumentationComment?: SymbolDisplayPart[]; 314 contextualSetAccessorDocumentationComment?: SymbolDisplayPart[]; 315 316 contextualGetAccessorTags?: JSDocTagInfo[]; 317 contextualSetAccessorTags?: JSDocTagInfo[]; 318 319 constructor(flags: SymbolFlags, name: __String) { 320 this.flags = flags; 321 this.escapedName = name; 322 } 323 324 getFlags(): SymbolFlags { 325 return this.flags; 326 } 327 328 get name(): string { 329 return symbolName(this); 330 } 331 332 getEscapedName(): __String { 333 return this.escapedName; 334 } 335 336 getName(): string { 337 return this.name; 338 } 339 340 getDeclarations(): Declaration[] | undefined { 341 return this.declarations; 342 } 343 344 getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] { 345 if (!this.documentationComment) { 346 this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs 347 348 if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { 349 const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; 350 this.documentationComment = getDocumentationComment([labelDecl], checker); 351 } 352 else { 353 this.documentationComment = getDocumentationComment(this.declarations, checker); 354 } 355 } 356 return this.documentationComment; 357 } 358 359 getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { 360 if (context) { 361 if (isGetAccessor(context)) { 362 if (!this.contextualGetAccessorDocumentationComment) { 363 this.contextualGetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isGetAccessor), checker); 364 } 365 if (length(this.contextualGetAccessorDocumentationComment)) { 366 return this.contextualGetAccessorDocumentationComment; 367 } 368 } 369 if (isSetAccessor(context)) { 370 if (!this.contextualSetAccessorDocumentationComment) { 371 this.contextualSetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isSetAccessor), checker); 372 } 373 if (length(this.contextualSetAccessorDocumentationComment)) { 374 return this.contextualSetAccessorDocumentationComment; 375 } 376 } 377 } 378 return this.getDocumentationComment(checker); 379 } 380 381 getJsDocTags(checker?: TypeChecker): JSDocTagInfo[] { 382 if (this.tags === undefined) { 383 this.tags = getJsDocTagsOfDeclarations(this.declarations, checker); 384 } 385 386 return this.tags; 387 } 388 389 getContextualJsDocTags(context: Node | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { 390 if (context) { 391 if (isGetAccessor(context)) { 392 if (!this.contextualGetAccessorTags) { 393 this.contextualGetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isGetAccessor), checker); 394 } 395 if (length(this.contextualGetAccessorTags)) { 396 return this.contextualGetAccessorTags; 397 } 398 } 399 if (isSetAccessor(context)) { 400 if (!this.contextualSetAccessorTags) { 401 this.contextualSetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isSetAccessor), checker); 402 } 403 if (length(this.contextualSetAccessorTags)) { 404 return this.contextualSetAccessorTags; 405 } 406 } 407 } 408 return this.getJsDocTags(checker); 409 } 410 } 411 412 class TokenObject<TKind extends SyntaxKind> extends TokenOrIdentifierObject implements Token<TKind> { 413 public kind: TKind; 414 415 constructor(kind: TKind, pos: number, end: number) { 416 super(pos, end); 417 this.kind = kind; 418 } 419 } 420 421 class IdentifierObject extends TokenOrIdentifierObject implements Identifier { 422 public kind: SyntaxKind.Identifier = SyntaxKind.Identifier; 423 public escapedText!: __String; 424 public autoGenerateFlags!: GeneratedIdentifierFlags; 425 _primaryExpressionBrand: any; 426 _memberExpressionBrand: any; 427 _leftHandSideExpressionBrand: any; 428 _updateExpressionBrand: any; 429 _unaryExpressionBrand: any; 430 _expressionBrand: any; 431 _declarationBrand: any; 432 /*@internal*/typeArguments!: NodeArray<TypeNode>; 433 constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) { 434 super(pos, end); 435 } 436 437 get text(): string { 438 return idText(this); 439 } 440 } 441 IdentifierObject.prototype.kind = SyntaxKind.Identifier; 442 class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { 443 public kind!: SyntaxKind.PrivateIdentifier; 444 public escapedText!: __String; 445 public symbol!: Symbol; 446 _primaryExpressionBrand: any; 447 _memberExpressionBrand: any; 448 _leftHandSideExpressionBrand: any; 449 _updateExpressionBrand: any; 450 _unaryExpressionBrand: any; 451 _expressionBrand: any; 452 constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) { 453 super(pos, end); 454 } 455 456 get text(): string { 457 return idText(this); 458 } 459 } 460 PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier; 461 462 class TypeObject implements Type { 463 checker: TypeChecker; 464 flags: TypeFlags; 465 objectFlags?: ObjectFlags; 466 id!: number; 467 symbol!: Symbol; 468 constructor(checker: TypeChecker, flags: TypeFlags) { 469 this.checker = checker; 470 this.flags = flags; 471 } 472 getFlags(): TypeFlags { 473 return this.flags; 474 } 475 getSymbol(): Symbol | undefined { 476 return this.symbol; 477 } 478 getProperties(): Symbol[] { 479 return this.checker.getPropertiesOfType(this); 480 } 481 getProperty(propertyName: string): Symbol | undefined { 482 return this.checker.getPropertyOfType(this, propertyName); 483 } 484 getApparentProperties(): Symbol[] { 485 return this.checker.getAugmentedPropertiesOfType(this); 486 } 487 getCallSignatures(): readonly Signature[] { 488 return this.checker.getSignaturesOfType(this, SignatureKind.Call); 489 } 490 getConstructSignatures(): readonly Signature[] { 491 return this.checker.getSignaturesOfType(this, SignatureKind.Construct); 492 } 493 getStringIndexType(): Type | undefined { 494 return this.checker.getIndexTypeOfType(this, IndexKind.String); 495 } 496 getNumberIndexType(): Type | undefined { 497 return this.checker.getIndexTypeOfType(this, IndexKind.Number); 498 } 499 getBaseTypes(): BaseType[] | undefined { 500 return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined; 501 } 502 isNullableType(): boolean { 503 return this.checker.isNullableType(this); 504 } 505 getNonNullableType(): Type { 506 return this.checker.getNonNullableType(this); 507 } 508 getNonOptionalType(): Type { 509 return this.checker.getNonOptionalType(this); 510 } 511 getConstraint(): Type | undefined { 512 return this.checker.getBaseConstraintOfType(this); 513 } 514 getDefault(): Type | undefined { 515 return this.checker.getDefaultFromTypeParameter(this); 516 } 517 518 isUnion(): this is UnionType { 519 return !!(this.flags & TypeFlags.Union); 520 } 521 isIntersection(): this is IntersectionType { 522 return !!(this.flags & TypeFlags.Intersection); 523 } 524 isUnionOrIntersection(): this is UnionOrIntersectionType { 525 return !!(this.flags & TypeFlags.UnionOrIntersection); 526 } 527 isLiteral(): this is LiteralType { 528 return !!(this.flags & TypeFlags.StringOrNumberLiteral); 529 } 530 isStringLiteral(): this is StringLiteralType { 531 return !!(this.flags & TypeFlags.StringLiteral); 532 } 533 isNumberLiteral(): this is NumberLiteralType { 534 return !!(this.flags & TypeFlags.NumberLiteral); 535 } 536 isTypeParameter(): this is TypeParameter { 537 return !!(this.flags & TypeFlags.TypeParameter); 538 } 539 isClassOrInterface(): this is InterfaceType { 540 return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface); 541 } 542 isClass(): this is InterfaceType { 543 return !!(getObjectFlags(this) & ObjectFlags.Class); 544 } 545 isIndexType(): this is IndexType { 546 return !!(this.flags & TypeFlags.Index); 547 } 548 /** 549 * This polyfills `referenceType.typeArguments` for API consumers 550 */ 551 get typeArguments() { 552 if (getObjectFlags(this) & ObjectFlags.Reference) { 553 return this.checker.getTypeArguments(this as Type as TypeReference); 554 } 555 return undefined; 556 } 557 } 558 559 class SignatureObject implements Signature { 560 flags: SignatureFlags; 561 checker: TypeChecker; 562 declaration!: SignatureDeclaration; 563 typeParameters?: TypeParameter[]; 564 parameters!: Symbol[]; 565 thisParameter!: Symbol; 566 resolvedReturnType!: Type; 567 resolvedTypePredicate: TypePredicate | undefined; 568 minTypeArgumentCount!: number; 569 minArgumentCount!: number; 570 571 // Undefined is used to indicate the value has not been computed. If, after computing, the 572 // symbol has no doc comment, then the empty array will be returned. 573 documentationComment?: SymbolDisplayPart[]; 574 jsDocTags?: JSDocTagInfo[]; // same 575 576 constructor(checker: TypeChecker, flags: SignatureFlags) { 577 this.checker = checker; 578 this.flags = flags; 579 } 580 581 getDeclaration(): SignatureDeclaration { 582 return this.declaration; 583 } 584 getTypeParameters(): TypeParameter[] | undefined { 585 return this.typeParameters; 586 } 587 getParameters(): Symbol[] { 588 return this.parameters; 589 } 590 getReturnType(): Type { 591 return this.checker.getReturnTypeOfSignature(this); 592 } 593 getTypeParameterAtPosition(pos: number): Type { 594 const type = this.checker.getParameterType(this, pos); 595 if (type.isIndexType() && isThisTypeParameter(type.type)) { 596 const constraint = type.type.getConstraint(); 597 if (constraint) { 598 return this.checker.getIndexType(constraint); 599 } 600 } 601 return type; 602 } 603 604 getDocumentationComment(): SymbolDisplayPart[] { 605 return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); 606 } 607 608 getJsDocTags(): JSDocTagInfo[] { 609 return this.jsDocTags || (this.jsDocTags = getJsDocTagsOfDeclarations(singleElementArray(this.declaration), this.checker)); 610 } 611 } 612 613 /** 614 * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it. 615 * @param node the Node in question. 616 * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`. 617 */ 618 function hasJSDocInheritDocTag(node: Node) { 619 return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc" || tag.tagName.text === "inheritdoc"); 620 } 621 622 function getJsDocTagsOfDeclarations(declarations: Declaration[] | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { 623 if (!declarations) return emptyArray; 624 625 let tags = JsDoc.getJsDocTagsFromDeclarations(declarations, checker); 626 if (checker && (tags.length === 0 || declarations.some(hasJSDocInheritDocTag))) { 627 const seenSymbols = new Set<Symbol>(); 628 for (const declaration of declarations) { 629 const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => { 630 if (!seenSymbols.has(symbol)) { 631 seenSymbols.add(symbol); 632 if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { 633 return symbol.getContextualJsDocTags(declaration, checker); 634 } 635 return symbol.declarations?.length === 1 ? symbol.getJsDocTags() : undefined; 636 } 637 }); 638 if (inheritedTags) { 639 tags = [...inheritedTags, ...tags]; 640 } 641 } 642 } 643 return tags; 644 } 645 646 function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { 647 if (!declarations) return emptyArray; 648 649 let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations, checker); 650 if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) { 651 const seenSymbols = new Set<Symbol>(); 652 for (const declaration of declarations) { 653 const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => { 654 if (!seenSymbols.has(symbol)) { 655 seenSymbols.add(symbol); 656 if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { 657 return symbol.getContextualDocumentationComment(declaration, checker); 658 } 659 return symbol.getDocumentationComment(checker); 660 } 661 }); 662 // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs 663 if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc); 664 } 665 } 666 return doc; 667 } 668 669 function findBaseOfDeclaration<T>(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined { 670 const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent; 671 if (!classOrInterfaceDeclaration) return; 672 673 const isStaticMember = hasStaticModifier(declaration); 674 return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => { 675 const baseType = checker.getTypeAtLocation(superTypeNode); 676 const type = isStaticMember && baseType.symbol ? checker.getTypeOfSymbol(baseType.symbol) : baseType; 677 const symbol = checker.getPropertyOfType(type, declaration.symbol.name); 678 return symbol ? cb(symbol) : undefined; 679 }); 680 } 681 682 class SourceFileObject extends NodeObject implements SourceFile { 683 public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile; 684 public _declarationBrand: any; 685 public fileName!: string; 686 public path!: Path; 687 public resolvedPath!: Path; 688 public originalFileName!: string; 689 public text!: string; 690 public scriptSnapshot!: IScriptSnapshot; 691 public lineMap!: readonly number[]; 692 693 public statements!: NodeArray<Statement>; 694 public endOfFileToken!: Token<SyntaxKind.EndOfFileToken>; 695 696 public amdDependencies!: { name: string; path: string }[]; 697 public moduleName!: string; 698 public referencedFiles!: FileReference[]; 699 public typeReferenceDirectives!: FileReference[]; 700 public libReferenceDirectives!: FileReference[]; 701 702 public syntacticDiagnostics!: DiagnosticWithLocation[]; 703 public parseDiagnostics!: DiagnosticWithLocation[]; 704 public bindDiagnostics!: DiagnosticWithLocation[]; 705 public bindSuggestionDiagnostics?: DiagnosticWithLocation[]; 706 707 public isDeclarationFile!: boolean; 708 public isDefaultLib!: boolean; 709 public hasNoDefaultLib!: boolean; 710 public externalModuleIndicator!: Node; // The first node that causes this file to be an external module 711 public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module 712 public nodeCount!: number; 713 public identifierCount!: number; 714 public symbolCount!: number; 715 public version!: string; 716 public scriptKind!: ScriptKind; 717 public languageVersion!: ScriptTarget; 718 public languageVariant!: LanguageVariant; 719 public identifiers!: ESMap<string, string>; 720 public nameTable: UnderscoreEscapedMap<number> | undefined; 721 public resolvedModules: ModeAwareCache<ResolvedModuleFull> | undefined; 722 public resolvedTypeReferenceDirectiveNames!: ModeAwareCache<ResolvedTypeReferenceDirective>; 723 public imports!: readonly StringLiteralLike[]; 724 public moduleAugmentations!: StringLiteral[]; 725 private namedDeclarations: ESMap<string, Declaration[]> | undefined; 726 public ambientModuleNames!: string[]; 727 public checkJsDirective: CheckJsDirective | undefined; 728 public errorExpectations: TextRange[] | undefined; 729 public possiblyContainDynamicImport?: boolean; 730 public pragmas!: PragmaMap; 731 public localJsxFactory: EntityName | undefined; 732 public localJsxNamespace: __String | undefined; 733 734 constructor(kind: SyntaxKind, pos: number, end: number) { 735 super(kind, pos, end); 736 } 737 738 public update(newText: string, textChangeRange: TextChangeRange): SourceFile { 739 return updateSourceFile(this, newText, textChangeRange); 740 } 741 742 public getLineAndCharacterOfPosition(position: number): LineAndCharacter { 743 return getLineAndCharacterOfPosition(this, position); 744 } 745 746 public getLineStarts(): readonly number[] { 747 return getLineStarts(this); 748 } 749 750 public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number { 751 return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits); 752 } 753 754 public getLineEndOfPosition(pos: number): number { 755 const { line } = this.getLineAndCharacterOfPosition(pos); 756 const lineStarts = this.getLineStarts(); 757 758 let lastCharPos: number | undefined; 759 if (line + 1 >= lineStarts.length) { 760 lastCharPos = this.getEnd(); 761 } 762 if (!lastCharPos) { 763 lastCharPos = lineStarts[line + 1] - 1; 764 } 765 766 const fullText = this.getFullText(); 767 // if the new line is "\r\n", we should return the last non-new-line-character position 768 return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos; 769 } 770 771 public getNamedDeclarations(): ESMap<string, Declaration[]> { 772 if (!this.namedDeclarations) { 773 this.namedDeclarations = this.computeNamedDeclarations(); 774 } 775 776 return this.namedDeclarations; 777 } 778 779 private computeNamedDeclarations(): ESMap<string, Declaration[]> { 780 const result = createMultiMap<Declaration>(); 781 782 this.forEachChild(visit); 783 784 return result; 785 786 function addDeclaration(declaration: Declaration) { 787 const name = getDeclarationName(declaration); 788 if (name) { 789 result.add(name, declaration); 790 } 791 } 792 793 function getDeclarations(name: string) { 794 let declarations = result.get(name); 795 if (!declarations) { 796 result.set(name, declarations = []); 797 } 798 return declarations; 799 } 800 801 function getDeclarationName(declaration: Declaration) { 802 const name = getNonAssignedNameOfDeclaration(declaration); 803 return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text 804 : isPropertyName(name) ? getNameFromPropertyName(name) : undefined); 805 } 806 807 function visit(node: Node): void { 808 switch (node.kind) { 809 case SyntaxKind.FunctionDeclaration: 810 case SyntaxKind.FunctionExpression: 811 case SyntaxKind.MethodDeclaration: 812 case SyntaxKind.MethodSignature: 813 const functionDeclaration = node as FunctionLikeDeclaration; 814 const declarationName = getDeclarationName(functionDeclaration); 815 816 if (declarationName) { 817 const declarations = getDeclarations(declarationName); 818 const lastDeclaration = lastOrUndefined(declarations); 819 820 // Check whether this declaration belongs to an "overload group". 821 if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) { 822 // Overwrite the last declaration if it was an overload 823 // and this one is an implementation. 824 if (functionDeclaration.body && !(lastDeclaration as FunctionLikeDeclaration).body) { 825 declarations[declarations.length - 1] = functionDeclaration; 826 } 827 } 828 else { 829 declarations.push(functionDeclaration); 830 } 831 } 832 forEachChild(node, visit); 833 break; 834 835 case SyntaxKind.ClassDeclaration: 836 case SyntaxKind.ClassExpression: 837 case SyntaxKind.StructDeclaration: 838 case SyntaxKind.InterfaceDeclaration: 839 case SyntaxKind.TypeAliasDeclaration: 840 case SyntaxKind.EnumDeclaration: 841 case SyntaxKind.ModuleDeclaration: 842 case SyntaxKind.ImportEqualsDeclaration: 843 case SyntaxKind.ExportSpecifier: 844 case SyntaxKind.ImportSpecifier: 845 case SyntaxKind.ImportClause: 846 case SyntaxKind.NamespaceImport: 847 case SyntaxKind.GetAccessor: 848 case SyntaxKind.SetAccessor: 849 case SyntaxKind.TypeLiteral: 850 addDeclaration(node as Declaration); 851 forEachChild(node, visit); 852 break; 853 854 case SyntaxKind.Parameter: 855 // Only consider parameter properties 856 if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { 857 break; 858 } 859 // falls through 860 861 case SyntaxKind.VariableDeclaration: 862 case SyntaxKind.BindingElement: { 863 const decl = node as VariableDeclaration; 864 if (isBindingPattern(decl.name)) { 865 forEachChild(decl.name, visit); 866 break; 867 } 868 if (decl.initializer) { 869 visit(decl.initializer); 870 } 871 } 872 // falls through 873 case SyntaxKind.EnumMember: 874 case SyntaxKind.PropertyDeclaration: 875 case SyntaxKind.PropertySignature: 876 addDeclaration(node as Declaration); 877 break; 878 879 case SyntaxKind.ExportDeclaration: 880 // Handle named exports case e.g.: 881 // export {a, b as B} from "mod"; 882 const exportDeclaration = node as ExportDeclaration; 883 if (exportDeclaration.exportClause) { 884 if (isNamedExports(exportDeclaration.exportClause)) { 885 forEach(exportDeclaration.exportClause.elements, visit); 886 } 887 else { 888 visit(exportDeclaration.exportClause.name); 889 } 890 } 891 break; 892 893 case SyntaxKind.ImportDeclaration: 894 const importClause = (node as ImportDeclaration).importClause; 895 if (importClause) { 896 // Handle default import case e.g.: 897 // import d from "mod"; 898 if (importClause.name) { 899 addDeclaration(importClause.name); 900 } 901 902 // Handle named bindings in imports e.g.: 903 // import * as NS from "mod"; 904 // import {a, b as B} from "mod"; 905 if (importClause.namedBindings) { 906 if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { 907 addDeclaration(importClause.namedBindings); 908 } 909 else { 910 forEach(importClause.namedBindings.elements, visit); 911 } 912 } 913 } 914 break; 915 916 case SyntaxKind.BinaryExpression: 917 if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) { 918 addDeclaration(node as BinaryExpression); 919 } 920 // falls through 921 922 default: 923 forEachChild(node, visit); 924 } 925 } 926 } 927 } 928 929 class SourceMapSourceObject implements SourceMapSource { 930 lineMap!: number[]; 931 constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { } 932 933 public getLineAndCharacterOfPosition(pos: number): LineAndCharacter { 934 return getLineAndCharacterOfPosition(this, pos); 935 } 936 } 937 938 function getServicesObjectAllocator(): ObjectAllocator { 939 return { 940 getNodeConstructor: () => NodeObject, 941 getTokenConstructor: () => TokenObject, 942 943 getIdentifierConstructor: () => IdentifierObject, 944 getPrivateIdentifierConstructor: () => PrivateIdentifierObject, 945 getSourceFileConstructor: () => SourceFileObject, 946 getSymbolConstructor: () => SymbolObject, 947 getTypeConstructor: () => TypeObject, 948 getSignatureConstructor: () => SignatureObject, 949 getSourceMapSourceConstructor: () => SourceMapSourceObject, 950 }; 951 } 952 953 /// Language Service 954 955 /* @internal */ 956 export interface DisplayPartsSymbolWriter extends EmitTextWriter { 957 displayParts(): SymbolDisplayPart[]; 958 } 959 960 /* @internal */ 961 export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings; 962 export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; 963 export function toEditorSettings(optionsAsMap: MapLike<any>): MapLike<any> { 964 let allPropertiesAreCamelCased = true; 965 for (const key in optionsAsMap) { 966 if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) { 967 allPropertiesAreCamelCased = false; 968 break; 969 } 970 } 971 if (allPropertiesAreCamelCased) { 972 return optionsAsMap; 973 } 974 const settings: MapLike<any> = {}; 975 for (const key in optionsAsMap) { 976 if (hasProperty(optionsAsMap, key)) { 977 const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1); 978 settings[newKey] = optionsAsMap[key]; 979 } 980 } 981 return settings; 982 } 983 984 function isCamelCase(s: string) { 985 return !s.length || s.charAt(0) === s.charAt(0).toLowerCase(); 986 } 987 988 export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) { 989 if (displayParts) { 990 return map(displayParts, displayPart => displayPart.text).join(""); 991 } 992 993 return ""; 994 } 995 996 export function getDefaultCompilerOptions(): CompilerOptions { 997 // Always default to "ScriptTarget.ES5" for the language service 998 return { 999 target: ScriptTarget.ES5, 1000 jsx: JsxEmit.Preserve 1001 }; 1002 } 1003 1004 export function getSupportedCodeFixes() { 1005 return codefix.getSupportedErrorCodes(); 1006 } 1007 1008 class SyntaxTreeCache { 1009 // For our syntactic only features, we also keep a cache of the syntax tree for the 1010 // currently edited file. 1011 private currentFileName: string | undefined; 1012 private currentFileVersion: string | undefined; 1013 private currentFileScriptSnapshot: IScriptSnapshot | undefined; 1014 private currentSourceFile: SourceFile | undefined; 1015 1016 constructor(private host: LanguageServiceHost) { 1017 } 1018 1019 public getCurrentSourceFile(fileName: string): SourceFile { 1020 const scriptSnapshot = this.host.getScriptSnapshot(fileName); 1021 if (!scriptSnapshot) { 1022 // The host does not know about this file. 1023 throw new Error("Could not find file: '" + fileName + "'."); 1024 } 1025 1026 const scriptKind = getScriptKind(fileName, this.host); 1027 const version = this.host.getScriptVersion(fileName); 1028 let sourceFile: SourceFile | undefined; 1029 1030 if (this.currentFileName !== fileName) { 1031 // This is a new file, just parse it 1032 const options: CreateSourceFileOptions = { 1033 languageVersion: ScriptTarget.Latest, 1034 impliedNodeFormat: getImpliedNodeFormatForFile( 1035 toPath(fileName, this.host.getCurrentDirectory(), this.host.getCompilerHost?.()?.getCanonicalFileName || hostGetCanonicalFileName(this.host)), 1036 this.host.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), 1037 this.host, 1038 this.host.getCompilationSettings() 1039 ), 1040 setExternalModuleIndicator: getSetExternalModuleIndicator(this.host.getCompilationSettings()) 1041 }; 1042 sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, scriptKind, this.host.getCompilationSettings()); 1043 } 1044 else if (this.currentFileVersion !== version) { 1045 // This is the same file, just a newer version. Incrementally parse the file. 1046 const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!); 1047 sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange, /*aggressiveChecks*/ undefined, this.host.getCompilationSettings()); 1048 } 1049 1050 if (sourceFile) { 1051 // All done, ensure state is up to date 1052 this.currentFileVersion = version; 1053 this.currentFileName = fileName; 1054 this.currentFileScriptSnapshot = scriptSnapshot; 1055 this.currentSourceFile = sourceFile; 1056 } 1057 1058 return this.currentSourceFile!; 1059 } 1060 } 1061 1062 function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) { 1063 sourceFile.version = version; 1064 sourceFile.scriptSnapshot = scriptSnapshot; 1065 } 1066 1067 export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTargetOrOptions: ScriptTarget | CreateSourceFileOptions, version: string, setNodeParents: boolean, scriptKind?: ScriptKind, option?: CompilerOptions): SourceFile { 1068 const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTargetOrOptions, setNodeParents, scriptKind, option); 1069 setSourceFileFields(sourceFile, scriptSnapshot, version); 1070 return sourceFile; 1071 } 1072 1073 export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean, option?: CompilerOptions): SourceFile { 1074 // If we were given a text change range, and our version or open-ness changed, then 1075 // incrementally parse this file. 1076 if (textChangeRange) { 1077 if (version !== sourceFile.version) { 1078 let newText: string; 1079 1080 // grab the fragment from the beginning of the original text to the beginning of the span 1081 const prefix = textChangeRange.span.start !== 0 1082 ? sourceFile.text.substr(0, textChangeRange.span.start) 1083 : ""; 1084 1085 // grab the fragment from the end of the span till the end of the original text 1086 const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length 1087 ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) 1088 : ""; 1089 1090 if (textChangeRange.newLength === 0) { 1091 // edit was a deletion - just combine prefix and suffix 1092 newText = prefix && suffix ? prefix + suffix : prefix || suffix; 1093 } 1094 else { 1095 // it was actual edit, fetch the fragment of new text that correspond to new span 1096 const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); 1097 // combine prefix, changed text and suffix 1098 newText = prefix && suffix 1099 ? prefix + changedText + suffix 1100 : prefix 1101 ? (prefix + changedText) 1102 : (changedText + suffix); 1103 } 1104 1105 const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks, option); 1106 setSourceFileFields(newSourceFile, scriptSnapshot, version); 1107 // after incremental parsing nameTable might not be up-to-date 1108 // drop it so it can be lazily recreated later 1109 newSourceFile.nameTable = undefined; 1110 1111 // dispose all resources held by old script snapshot 1112 if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { 1113 if (sourceFile.scriptSnapshot.dispose) { 1114 sourceFile.scriptSnapshot.dispose(); 1115 } 1116 1117 sourceFile.scriptSnapshot = undefined; 1118 } 1119 1120 return newSourceFile; 1121 } 1122 } 1123 1124 const options: CreateSourceFileOptions = { 1125 languageVersion: sourceFile.languageVersion, 1126 impliedNodeFormat: sourceFile.impliedNodeFormat, 1127 setExternalModuleIndicator: sourceFile.setExternalModuleIndicator, 1128 }; 1129 // Otherwise, just create a new source file. 1130 return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, sourceFile.scriptKind, option); 1131 } 1132 1133 const NoopCancellationToken: CancellationToken = { 1134 isCancellationRequested: returnFalse, 1135 throwIfCancellationRequested: noop, 1136 }; 1137 1138 class CancellationTokenObject implements CancellationToken { 1139 constructor(private cancellationToken: HostCancellationToken) { 1140 } 1141 1142 public isCancellationRequested(): boolean { 1143 return this.cancellationToken.isCancellationRequested(); 1144 } 1145 1146 public throwIfCancellationRequested(): void { 1147 if (this.isCancellationRequested()) { 1148 tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "CancellationTokenObject" }); 1149 throw new OperationCanceledException(); 1150 } 1151 } 1152 } 1153 1154 /* @internal */ 1155 /** A cancellation that throttles calls to the host */ 1156 export class ThrottledCancellationToken implements CancellationToken { 1157 // Store when we last tried to cancel. Checking cancellation can be expensive (as we have 1158 // to marshall over to the host layer). So we only bother actually checking once enough 1159 // time has passed. 1160 private lastCancellationCheckTime = 0; 1161 1162 constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) { 1163 } 1164 1165 public isCancellationRequested(): boolean { 1166 const time = timestamp(); 1167 const duration = Math.abs(time - this.lastCancellationCheckTime); 1168 if (duration >= this.throttleWaitMilliseconds) { 1169 // Check no more than once every throttle wait milliseconds 1170 this.lastCancellationCheckTime = time; 1171 return this.hostCancellationToken.isCancellationRequested(); 1172 } 1173 1174 return false; 1175 } 1176 1177 public throwIfCancellationRequested(): void { 1178 if (this.isCancellationRequested()) { 1179 tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "ThrottledCancellationToken" }); 1180 throw new OperationCanceledException(); 1181 } 1182 } 1183 } 1184 1185 const invalidOperationsInPartialSemanticMode: readonly (keyof LanguageService)[] = [ 1186 "getSemanticDiagnostics", 1187 "getSuggestionDiagnostics", 1188 "getCompilerOptionsDiagnostics", 1189 "getSemanticClassifications", 1190 "getEncodedSemanticClassifications", 1191 "getCodeFixesAtPosition", 1192 "getCombinedCodeFix", 1193 "applyCodeActionCommand", 1194 "organizeImports", 1195 "getEditsForFileRename", 1196 "getEmitOutput", 1197 "getApplicableRefactors", 1198 "getEditsForRefactor", 1199 "prepareCallHierarchy", 1200 "provideCallHierarchyIncomingCalls", 1201 "provideCallHierarchyOutgoingCalls", 1202 "provideInlayHints" 1203 ]; 1204 1205 const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [ 1206 ...invalidOperationsInPartialSemanticMode, 1207 "getCompletionsAtPosition", 1208 "getCompletionEntryDetails", 1209 "getCompletionEntrySymbol", 1210 "getSignatureHelpItems", 1211 "getQuickInfoAtPosition", 1212 "getDefinitionAtPosition", 1213 "getDefinitionAndBoundSpan", 1214 "getImplementationAtPosition", 1215 "getTypeDefinitionAtPosition", 1216 "getReferencesAtPosition", 1217 "findReferences", 1218 "getOccurrencesAtPosition", 1219 "getDocumentHighlights", 1220 "getNavigateToItems", 1221 "getRenameInfo", 1222 "findRenameLocations", 1223 "getApplicableRefactors", 1224 ]; 1225 export function createLanguageService( 1226 host: LanguageServiceHost, 1227 documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), 1228 syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode, 1229 ): LanguageService { 1230 let languageServiceMode: LanguageServiceMode; 1231 if (syntaxOnlyOrLanguageServiceMode === undefined) { 1232 languageServiceMode = LanguageServiceMode.Semantic; 1233 } 1234 else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") { 1235 // languageServiceMode = SyntaxOnly 1236 languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.Syntactic : LanguageServiceMode.Semantic; 1237 } 1238 else { 1239 languageServiceMode = syntaxOnlyOrLanguageServiceMode; 1240 } 1241 1242 const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); 1243 let program: Program; 1244 let builderProgram: BuilderProgram; 1245 let lastProjectVersion: string; 1246 let lastTypesRootVersion = 0; 1247 1248 const cancellationToken = host.getCancellationToken 1249 ? new CancellationTokenObject(host.getCancellationToken()) 1250 : NoopCancellationToken; 1251 1252 const currentDirectory = host.getCurrentDirectory(); 1253 1254 // Checks if the localized messages json is set, and if not, query the host for it 1255 maybeSetLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages?.bind(host)); 1256 1257 function log(message: string) { 1258 if (host.log) { 1259 host.log(message); 1260 } 1261 } 1262 1263 const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); 1264 const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); 1265 1266 const sourceMapper = getSourceMapper({ 1267 useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, 1268 getCurrentDirectory: () => currentDirectory, 1269 getProgram, 1270 fileExists: maybeBind(host, host.fileExists), 1271 readFile: maybeBind(host, host.readFile), 1272 getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), 1273 getSourceFileLike: maybeBind(host, host.getSourceFileLike), 1274 log 1275 }); 1276 1277 function getValidSourceFile(fileName: string): SourceFile { 1278 const sourceFile = program.getSourceFile(fileName); 1279 if (!sourceFile) { 1280 const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`); 1281 1282 // We've been having trouble debugging this, so attach sidecar data for the tsserver log. 1283 // See https://github.com/microsoft/TypeScript/issues/30180. 1284 error.ProgramFiles = program.getSourceFiles().map(f => f.fileName); 1285 1286 throw error; 1287 } 1288 return sourceFile; 1289 } 1290 1291 function synchronizeHostData(isCreateIncrementalProgramMode: boolean = false, withLinterProgram?: boolean): void { 1292 Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic); 1293 // perform fast check if host supports it 1294 if (host.getProjectVersion) { 1295 const hostProjectVersion = host.getProjectVersion(); 1296 if (hostProjectVersion) { 1297 if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames?.()) { 1298 return; 1299 } 1300 1301 lastProjectVersion = hostProjectVersion; 1302 } 1303 } 1304 1305 const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; 1306 if (lastTypesRootVersion !== typeRootsVersion) { 1307 log("TypeRoots version has changed; provide new program"); 1308 program = undefined!; // TODO: GH#18217 1309 lastTypesRootVersion = typeRootsVersion; 1310 } 1311 1312 // This array is retained by the program and will be used to determine if the program is up to date, 1313 // so we need to make a copy in case the host mutates the underlying array - otherwise it would look 1314 // like every program always has the host's current list of root files. 1315 const rootFileNames = host.getScriptFileNames().slice(); 1316 1317 // Get a fresh cache of the host information 1318 const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); 1319 const hasInvalidatedResolutions: HasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse; 1320 const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames); 1321 const projectReferences = host.getProjectReferences?.(); 1322 let parsedCommandLines: ESMap<Path, ParsedCommandLine | false> | undefined; 1323 1324 // Now create a new compiler 1325 let compilerHost: CompilerHost | undefined = { 1326 getSourceFile: getOrCreateSourceFile, 1327 getSourceFileByPath: getOrCreateSourceFileByPath, 1328 getCancellationToken: () => cancellationToken, 1329 getCanonicalFileName, 1330 useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, 1331 getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), 1332 getDefaultLibFileName: options => host.getDefaultLibFileName(options), 1333 writeFile: noop, 1334 getCurrentDirectory: () => currentDirectory, 1335 fileExists: fileName => host.fileExists(fileName), 1336 readFile: fileName => host.readFile && host.readFile(fileName), 1337 getSymlinkCache: maybeBind(host, host.getSymlinkCache), 1338 realpath: maybeBind(host, host.realpath), 1339 directoryExists: directoryName => { 1340 return directoryProbablyExists(directoryName, host); 1341 }, 1342 getDirectories: path => { 1343 return host.getDirectories ? host.getDirectories(path) : []; 1344 }, 1345 readDirectory: (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) => { 1346 Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'"); 1347 return host.readDirectory!(path, extensions, exclude, include, depth); 1348 }, 1349 onReleaseOldSourceFile, 1350 onReleaseParsedCommandLine, 1351 hasInvalidatedResolutions, 1352 hasChangedAutomaticTypeDirectiveNames, 1353 trace: maybeBind(host, host.trace), 1354 resolveModuleNames: maybeBind(host, host.resolveModuleNames), 1355 getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache), 1356 resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives), 1357 useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect), 1358 getParsedCommandLine, 1359 getJsDocNodeCheckedConfig: maybeBind(host, host.getJsDocNodeCheckedConfig), 1360 getJsDocNodeConditionCheckedResult: maybeBind(host, host.getJsDocNodeConditionCheckedResult), 1361 getFileCheckedModuleInfo: maybeBind(host, host.getFileCheckedModuleInfo), 1362 }; 1363 1364 const originalGetSourceFile = compilerHost.getSourceFile; 1365 1366 const { getSourceFileWithCache } = changeCompilerHostLikeToUseCache( 1367 compilerHost, 1368 fileName => toPath(fileName, currentDirectory, getCanonicalFileName), 1369 (...args) => originalGetSourceFile.call(compilerHost, ...args) 1370 ); 1371 compilerHost.getSourceFile = getSourceFileWithCache!; 1372 1373 host.setCompilerHost?.(compilerHost); 1374 1375 const parseConfigHost: ParseConfigFileHost = { 1376 useCaseSensitiveFileNames, 1377 fileExists: fileName => compilerHost!.fileExists(fileName), 1378 readFile: fileName => compilerHost!.readFile(fileName), 1379 readDirectory: (...args) => compilerHost!.readDirectory!(...args), 1380 trace: compilerHost.trace, 1381 getCurrentDirectory: compilerHost.getCurrentDirectory, 1382 onUnRecoverableConfigFileDiagnostic: noop, 1383 }; 1384 1385 // The call to isProgramUptoDate below may refer back to documentRegistryBucketKey; 1386 // calculate this early so it's not undefined if downleveled to a var (or, if emitted 1387 // as a const variable without downleveling, doesn't crash). 1388 const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); 1389 1390 // If the program is already up-to-date, we can reuse it 1391 if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { 1392 return; 1393 } 1394 1395 // clear ArkUI collected properties 1396 host.clearProps && host.clearProps(); 1397 1398 compilerHost.getLastCompiledProgram = () => { return program; } 1399 1400 // IMPORTANT - It is critical from this moment onward that we do not check 1401 // cancellation tokens. We are about to mutate source files from a previous program 1402 // instance. If we cancel midway through, we may end up in an inconsistent state where 1403 // the program points to old source files that have been invalidated because of 1404 // incremental parsing. 1405 1406 const options: CreateProgramOptions = { 1407 rootNames: rootFileNames, 1408 options: newSettings, 1409 host: compilerHost, 1410 oldProgram: program, 1411 projectReferences 1412 }; 1413 1414 if (isCreateIncrementalProgramMode) { 1415 if (withLinterProgram) { 1416 builderProgram = createIncrementalProgramForArkTs(options); 1417 } else { 1418 builderProgram = createIncrementalProgram(options); 1419 } 1420 program = builderProgram.getProgram(); 1421 } else { 1422 program = createProgram(options); 1423 } 1424 1425 // 'getOrCreateSourceFile' depends on caching but should be used past this point. 1426 // After this point, the cache needs to be cleared to allow all collected snapshots to be released 1427 compilerHost = undefined; 1428 parsedCommandLines = undefined; 1429 1430 // We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above, 1431 // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during 1432 // the course of whatever called `synchronizeHostData` 1433 sourceMapper.clearCache(); 1434 1435 // Make sure all the nodes in the program are both bound, and have their parent 1436 // pointers set property. 1437 program.getTypeChecker(); 1438 return; 1439 1440 function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined { 1441 const path = toPath(fileName, currentDirectory, getCanonicalFileName); 1442 const existing = parsedCommandLines?.get(path); 1443 if (existing !== undefined) return existing || undefined; 1444 1445 const result = host.getParsedCommandLine ? 1446 host.getParsedCommandLine(fileName) : 1447 getParsedCommandLineOfConfigFileUsingSourceFile(fileName); 1448 (parsedCommandLines ||= new Map()).set(path, result || false); 1449 return result; 1450 } 1451 1452 function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined { 1453 const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined; 1454 if (!result) return undefined; 1455 result.path = toPath(configFileName, currentDirectory, getCanonicalFileName); 1456 result.resolvedPath = result.path; 1457 result.originalFileName = result.fileName; 1458 return parseJsonSourceFileConfigFileContent( 1459 result, 1460 parseConfigHost, 1461 getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), 1462 /*optionsToExtend*/ undefined, 1463 getNormalizedAbsolutePath(configFileName, currentDirectory), 1464 ); 1465 } 1466 1467 function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) { 1468 if (host.getParsedCommandLine) { 1469 host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions); 1470 } 1471 else if (oldResolvedRef) { 1472 onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions); 1473 } 1474 } 1475 1476 // Release any files we have acquired in the old program but are 1477 // not part of the new program. 1478 function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { 1479 const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); 1480 documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); 1481 } 1482 1483 function getOrCreateSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { 1484 return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersionOrOptions, onError, shouldCreateNewSourceFile); 1485 } 1486 1487 function getOrCreateSourceFileByPath(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { 1488 Debug.assert(compilerHost, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host."); 1489 // The program is asking for this file, check first if the host can locate it. 1490 // If the host can not locate the file, then it does not exist. return undefined 1491 // to the program to allow reporting of errors for missing files. 1492 const scriptSnapshot = host.getScriptSnapshot(fileName); 1493 if (!scriptSnapshot) { 1494 return undefined; 1495 } 1496 1497 const scriptKind = getScriptKind(fileName, host); 1498 const scriptVersion = host.getScriptVersion(fileName); 1499 1500 // Check if the language version has changed since we last created a program; if they are the same, 1501 // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile 1502 // can not be reused. we have to dump all syntax trees and create new ones. 1503 if (!shouldCreateNewSourceFile) { 1504 // Check if the old program had this file already 1505 const oldSourceFile = program && program.getSourceFileByPath(path); 1506 if (oldSourceFile) { 1507 // We already had a source file for this file name. Go to the registry to 1508 // ensure that we get the right up to date version of it. We need this to 1509 // address the following race-condition. Specifically, say we have the following: 1510 // 1511 // LS1 1512 // \ 1513 // DocumentRegistry 1514 // / 1515 // LS2 1516 // 1517 // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates 1518 // it's version of 'foo.ts' to version 2. This will cause LS2 and the 1519 // DocumentRegistry to have version 2 of the document. However, LS1 will 1520 // have version 1. And *importantly* this source file will be *corrupt*. 1521 // The act of creating version 2 of the file irrevocably damages the version 1522 // 1 file. 1523 // 1524 // So, later when we call into LS1, we need to make sure that it doesn't use 1525 // it's source file any more, and instead defers to DocumentRegistry to get 1526 // either version 1, version 2 (or some other version) depending on what the 1527 // host says should be used. 1528 1529 // We do not support the scenario where a host can modify a registered 1530 // file's script kind, i.e. in one project some file is treated as ".ts" 1531 // and in another as ".js" 1532 if (scriptKind === oldSourceFile.scriptKind) { 1533 return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); 1534 } 1535 else { 1536 // Release old source file and fall through to aquire new file with new script kind 1537 documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); 1538 } 1539 } 1540 1541 // We didn't already have the file. Fall through and acquire it from the registry. 1542 } 1543 1544 // Could not find this file in the old program, create a new SourceFile for it. 1545 return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); 1546 } 1547 } 1548 1549 // TODO: GH#18217 frequently asserted as defined 1550 function getProgram(): Program | undefined { 1551 if (languageServiceMode === LanguageServiceMode.Syntactic) { 1552 Debug.assert(program === undefined); 1553 return undefined; 1554 } 1555 1556 synchronizeHostData(); 1557 1558 return program; 1559 } 1560 1561 function getBuilderProgram(withLinterProgram?: boolean): BuilderProgram | undefined { 1562 if (languageServiceMode === LanguageServiceMode.Syntactic) { 1563 Debug.assert(builderProgram === undefined); 1564 return undefined; 1565 } 1566 1567 synchronizeHostData(isIncrementalCompilation(host.getCompilationSettings()), withLinterProgram); 1568 1569 return builderProgram; 1570 } 1571 1572 function getAutoImportProvider(): Program | undefined { 1573 return host.getPackageJsonAutoImportProvider?.(); 1574 } 1575 1576 function updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set<DocumentSpan>): boolean { 1577 const checker = program.getTypeChecker(); 1578 const symbol = getSymbolForProgram(); 1579 1580 if (!symbol) return false; 1581 1582 for (const referencedSymbol of referencedSymbols) { 1583 for (const ref of referencedSymbol.references) { 1584 const refNode = getNodeForSpan(ref); 1585 Debug.assertIsDefined(refNode); 1586 if (knownSymbolSpans.has(ref) || FindAllReferences.isDeclarationOfSymbol(refNode, symbol)) { 1587 knownSymbolSpans.add(ref); 1588 ref.isDefinition = true; 1589 const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); 1590 if (mappedSpan) { 1591 knownSymbolSpans.add(mappedSpan); 1592 } 1593 } 1594 else { 1595 ref.isDefinition = false; 1596 } 1597 } 1598 } 1599 1600 return true; 1601 1602 function getSymbolForProgram(): Symbol | undefined { 1603 for (const referencedSymbol of referencedSymbols) { 1604 for (const ref of referencedSymbol.references) { 1605 if (knownSymbolSpans.has(ref)) { 1606 const refNode = getNodeForSpan(ref); 1607 Debug.assertIsDefined(refNode); 1608 return checker.getSymbolAtLocation(refNode); 1609 } 1610 const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); 1611 if (mappedSpan && knownSymbolSpans.has(mappedSpan)) { 1612 const refNode = getNodeForSpan(mappedSpan); 1613 if (refNode) { 1614 return checker.getSymbolAtLocation(refNode); 1615 } 1616 } 1617 } 1618 } 1619 1620 return undefined; 1621 } 1622 1623 function getNodeForSpan(docSpan: DocumentSpan): Node | undefined { 1624 const sourceFile = program.getSourceFile(docSpan.fileName); 1625 if (!sourceFile) return undefined; 1626 const rawNode = getTouchingPropertyName(sourceFile, docSpan.textSpan.start); 1627 const adjustedNode = FindAllReferences.Core.getAdjustedNode(rawNode, { use: FindAllReferences.FindReferencesUse.References }); 1628 return adjustedNode; 1629 } 1630 } 1631 1632 function cleanupSemanticCache(): void { 1633 program = undefined!; // TODO: GH#18217 1634 } 1635 1636 function dispose(): void { 1637 if (program) { 1638 // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host 1639 const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()); 1640 forEach(program.getSourceFiles(), f => 1641 documentRegistry.releaseDocumentWithKey(f.resolvedPath, key, f.scriptKind, f.impliedNodeFormat)); 1642 program = undefined!; // TODO: GH#18217 1643 } 1644 host = undefined!; 1645 } 1646 1647 /// Diagnostics 1648 function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] { 1649 synchronizeHostData(); 1650 1651 return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice(); 1652 } 1653 1654 /** 1655 * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors 1656 * If '-d' enabled, report both semantic and emitter errors 1657 */ 1658 function getSemanticDiagnostics(fileName: string): Diagnostic[] { 1659 synchronizeHostData(); 1660 1661 const targetSourceFile = getValidSourceFile(fileName); 1662 1663 // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file. 1664 // Therefore only get diagnostics for given file. 1665 1666 const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken); 1667 if (!getEmitDeclarations(program.getCompilerOptions())) { 1668 return semanticDiagnostics.slice(); 1669 } 1670 1671 // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface 1672 const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken); 1673 return [...semanticDiagnostics, ...declarationDiagnostics]; 1674 } 1675 1676 function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] { 1677 synchronizeHostData(); 1678 return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken); 1679 } 1680 1681 function getCompilerOptionsDiagnostics() { 1682 synchronizeHostData(); 1683 return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; 1684 } 1685 1686 function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions, formattingSettings?: FormatCodeSettings): CompletionInfo | undefined { 1687 // Convert from deprecated options names to new names 1688 const fullPreferences: UserPreferences = { 1689 ...identity<UserPreferences>(options), // avoid excess property check 1690 includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports, 1691 includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions, 1692 }; 1693 synchronizeHostData(); 1694 return Completions.getCompletionsAtPosition( 1695 host, 1696 program, 1697 log, 1698 getValidSourceFile(fileName), 1699 position, 1700 fullPreferences, 1701 options.triggerCharacter, 1702 options.triggerKind, 1703 cancellationToken, 1704 formattingSettings && formatting.getFormatContext(formattingSettings, host)); 1705 } 1706 1707 function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { 1708 synchronizeHostData(); 1709 return Completions.getCompletionEntryDetails( 1710 program, 1711 log, 1712 getValidSourceFile(fileName), 1713 position, 1714 { name, source, data }, 1715 host, 1716 (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217 1717 preferences, 1718 cancellationToken, 1719 ); 1720 } 1721 1722 function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined { 1723 synchronizeHostData(); 1724 return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences); 1725 } 1726 1727 function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined { 1728 synchronizeHostData(); 1729 1730 const sourceFile = getValidSourceFile(fileName); 1731 const node = getTouchingPropertyName(sourceFile, position); 1732 if (node === sourceFile) { 1733 // Avoid giving quickInfo for the sourceFile as a whole. 1734 return undefined; 1735 } 1736 1737 const typeChecker = program.getTypeChecker(); 1738 const nodeForQuickInfo = getNodeForQuickInfo(node); 1739 const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker); 1740 1741 if (!symbol || typeChecker.isUnknownSymbol(symbol)) { 1742 const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined; 1743 return type && { 1744 kind: ScriptElementKind.unknown, 1745 kindModifiers: ScriptElementKindModifier.none, 1746 textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), 1747 displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))), 1748 documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined, 1749 tags: type.symbol ? type.symbol.getJsDocTags(typeChecker) : undefined 1750 }; 1751 } 1752 1753 const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker => 1754 SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo) 1755 ); 1756 return { 1757 kind: symbolKind, 1758 kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), 1759 textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), 1760 displayParts, 1761 documentation, 1762 tags, 1763 }; 1764 } 1765 1766 function getNodeForQuickInfo(node: Node): Node { 1767 if (isNewExpression(node.parent) && node.pos === node.parent.pos) { 1768 return node.parent.expression; 1769 } 1770 if (isNamedTupleMember(node.parent) && node.pos === node.parent.pos) { 1771 return node.parent; 1772 } 1773 if (isImportMeta(node.parent) && node.parent.name === node) { 1774 return node.parent; 1775 } 1776 return node; 1777 } 1778 1779 function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean { 1780 switch (node.kind) { 1781 case SyntaxKind.Identifier: 1782 return !isLabelName(node) && !isTagName(node) && !isConstTypeReference(node.parent); 1783 case SyntaxKind.PropertyAccessExpression: 1784 case SyntaxKind.QualifiedName: 1785 // Don't return quickInfo if inside the comment in `a/**/.b` 1786 return !isInComment(sourceFile, position); 1787 case SyntaxKind.ThisKeyword: 1788 case SyntaxKind.ThisType: 1789 case SyntaxKind.SuperKeyword: 1790 case SyntaxKind.NamedTupleMember: 1791 return true; 1792 case SyntaxKind.MetaProperty: 1793 return isImportMeta(node); 1794 default: 1795 return false; 1796 } 1797 } 1798 1799 /// Goto definition 1800 function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined { 1801 synchronizeHostData(); 1802 return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly, stopAtAlias); 1803 } 1804 1805 function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined { 1806 synchronizeHostData(); 1807 return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position); 1808 } 1809 1810 function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { 1811 synchronizeHostData(); 1812 return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); 1813 } 1814 1815 /// Goto implementation 1816 1817 function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { 1818 synchronizeHostData(); 1819 return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); 1820 } 1821 1822 /// References and Occurrences 1823 function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined { 1824 return flatMap( 1825 getDocumentHighlights(fileName, position, [fileName]), 1826 entry => entry.highlightSpans.map<ReferenceEntry>(highlightSpan => ({ 1827 fileName: entry.fileName, 1828 textSpan: highlightSpan.textSpan, 1829 isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference, 1830 ...highlightSpan.isInString && { isInString: true }, 1831 ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan } 1832 })) 1833 ); 1834 } 1835 1836 function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined { 1837 const normalizedFileName = normalizePath(fileName); 1838 Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName)); 1839 synchronizeHostData(); 1840 const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName)); 1841 const sourceFile = getValidSourceFile(fileName); 1842 return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch); 1843 } 1844 1845 function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined { 1846 synchronizeHostData(); 1847 const sourceFile = getValidSourceFile(fileName); 1848 const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); 1849 if (!Rename.nodeIsEligibleForRename(node)) return undefined; 1850 if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) { 1851 const { openingElement, closingElement } = node.parent.parent; 1852 return [openingElement, closingElement].map((node): RenameLocation => { 1853 const textSpan = createTextSpanFromNode(node.tagName, sourceFile); 1854 return { 1855 fileName: sourceFile.fileName, 1856 textSpan, 1857 ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent) 1858 }; 1859 }); 1860 } 1861 else { 1862 return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename }, 1863 (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false)); 1864 } 1865 } 1866 1867 function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { 1868 synchronizeHostData(); 1869 return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, FindAllReferences.toReferenceEntry); 1870 } 1871 1872 function getReferencesWorker<T>(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry<T>): T[] | undefined { 1873 synchronizeHostData(); 1874 1875 // Exclude default library when renaming as commonly user don't want to change that file. 1876 const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename 1877 ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) 1878 : program.getSourceFiles(); 1879 1880 return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); 1881 } 1882 1883 function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { 1884 synchronizeHostData(); 1885 return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); 1886 } 1887 1888 function getFileReferences(fileName: string): ReferenceEntry[] { 1889 synchronizeHostData(); 1890 return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(FindAllReferences.toReferenceEntry); 1891 } 1892 1893 function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] { 1894 synchronizeHostData(); 1895 const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles(); 1896 return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); 1897 } 1898 1899 function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) { 1900 synchronizeHostData(); 1901 1902 const sourceFile = getValidSourceFile(fileName); 1903 const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); 1904 return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit); 1905 } 1906 1907 // Signature help 1908 /** 1909 * This is a semantic operation. 1910 */ 1911 function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined { 1912 synchronizeHostData(); 1913 1914 const sourceFile = getValidSourceFile(fileName); 1915 1916 return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken); 1917 } 1918 1919 /// Syntactic features 1920 function getNonBoundSourceFile(fileName: string): SourceFile { 1921 return syntaxTreeCache.getCurrentSourceFile(fileName); 1922 } 1923 1924 function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined { 1925 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 1926 1927 // Get node at the location 1928 const node = getTouchingPropertyName(sourceFile, startPos); 1929 1930 if (node === sourceFile) { 1931 return undefined; 1932 } 1933 1934 switch (node.kind) { 1935 case SyntaxKind.PropertyAccessExpression: 1936 case SyntaxKind.QualifiedName: 1937 case SyntaxKind.StringLiteral: 1938 case SyntaxKind.FalseKeyword: 1939 case SyntaxKind.TrueKeyword: 1940 case SyntaxKind.NullKeyword: 1941 case SyntaxKind.SuperKeyword: 1942 case SyntaxKind.ThisKeyword: 1943 case SyntaxKind.ThisType: 1944 case SyntaxKind.Identifier: 1945 break; 1946 1947 // Cant create the text span 1948 default: 1949 return undefined; 1950 } 1951 1952 let nodeForStartPos = node; 1953 while (true) { 1954 if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) { 1955 // If on the span is in right side of the the property or qualified name, return the span from the qualified name pos to end of this node 1956 nodeForStartPos = nodeForStartPos.parent; 1957 } 1958 else if (isNameOfModuleDeclaration(nodeForStartPos)) { 1959 // If this is name of a module declarations, check if this is right side of dotted module name 1960 // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of 1961 // Then this name is name from dotted module 1962 if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration && 1963 (nodeForStartPos.parent.parent as ModuleDeclaration).body === nodeForStartPos.parent) { 1964 // Use parent module declarations name for start pos 1965 nodeForStartPos = (nodeForStartPos.parent.parent as ModuleDeclaration).name; 1966 } 1967 else { 1968 // We have to use this name for start pos 1969 break; 1970 } 1971 } 1972 else { 1973 // Is not a member expression so we have found the node for start pos 1974 break; 1975 } 1976 } 1977 1978 return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd()); 1979 } 1980 1981 function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined { 1982 // doesn't use compiler - no need to synchronize with host 1983 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 1984 1985 return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position); 1986 } 1987 1988 function getNavigationBarItems(fileName: string): NavigationBarItem[] { 1989 return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); 1990 } 1991 1992 function getNavigationTree(fileName: string): NavigationTree { 1993 return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); 1994 } 1995 1996 function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; 1997 function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] { 1998 synchronizeHostData(); 1999 2000 const responseFormat = format || SemanticClassificationFormat.Original; 2001 if (responseFormat === SemanticClassificationFormat.TwentyTwenty) { 2002 return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); 2003 } 2004 else { 2005 return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); 2006 } 2007 } 2008 2009 function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications { 2010 synchronizeHostData(); 2011 2012 const responseFormat = format || SemanticClassificationFormat.Original; 2013 if (responseFormat === SemanticClassificationFormat.Original) { 2014 return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); 2015 } 2016 else { 2017 return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); 2018 } 2019 } 2020 2021 function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { 2022 // doesn't use compiler - no need to synchronize with host 2023 return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); 2024 } 2025 2026 function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications { 2027 // doesn't use compiler - no need to synchronize with host 2028 return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); 2029 } 2030 2031 function getOutliningSpans(fileName: string): OutliningSpan[] { 2032 // doesn't use compiler - no need to synchronize with host 2033 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2034 return OutliningElementsCollector.collectElements(sourceFile, cancellationToken); 2035 } 2036 2037 const braceMatching = new Map(getEntries({ 2038 [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken, 2039 [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken, 2040 [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken, 2041 [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken, 2042 })); 2043 braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind)); 2044 2045 function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { 2046 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2047 const token = getTouchingToken(sourceFile, position); 2048 const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined; 2049 const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile); 2050 // We want to order the braces when we return the result. 2051 return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray; 2052 } 2053 2054 function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) { 2055 let start = timestamp(); 2056 const settings = toEditorSettings(editorOptions); 2057 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2058 log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start)); 2059 2060 start = timestamp(); 2061 2062 const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings); 2063 log("getIndentationAtPosition: computeIndentation : " + (timestamp() - start)); 2064 2065 return result; 2066 } 2067 2068 function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { 2069 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2070 return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host)); 2071 } 2072 2073 function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { 2074 return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host)); 2075 } 2076 2077 function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { 2078 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2079 const formatContext = formatting.getFormatContext(toEditorSettings(options), host); 2080 2081 if (!isInComment(sourceFile, position)) { 2082 switch (key) { 2083 case "{": 2084 return formatting.formatOnOpeningCurly(position, sourceFile, formatContext); 2085 case "}": 2086 return formatting.formatOnClosingCurly(position, sourceFile, formatContext); 2087 case ";": 2088 return formatting.formatOnSemicolon(position, sourceFile, formatContext); 2089 case "\n": 2090 return formatting.formatOnEnter(position, sourceFile, formatContext); 2091 } 2092 } 2093 2094 return []; 2095 } 2096 2097 function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] { 2098 synchronizeHostData(); 2099 const sourceFile = getValidSourceFile(fileName); 2100 const span = createTextSpanFromBounds(start, end); 2101 const formatContext = formatting.getFormatContext(formatOptions, host); 2102 2103 return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => { 2104 cancellationToken.throwIfCancellationRequested(); 2105 return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences }); 2106 }); 2107 } 2108 2109 function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions { 2110 synchronizeHostData(); 2111 Debug.assert(scope.type === "file"); 2112 const sourceFile = getValidSourceFile(scope.fileName); 2113 const formatContext = formatting.getFormatContext(formatOptions, host); 2114 2115 return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences }); 2116 } 2117 2118 function organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { 2119 synchronizeHostData(); 2120 Debug.assert(args.type === "file"); 2121 const sourceFile = getValidSourceFile(args.fileName); 2122 const formatContext = formatting.getFormatContext(formatOptions, host); 2123 2124 const mode = args.mode ?? (args.skipDestructiveCodeActions ? OrganizeImportsMode.SortAndCombine : OrganizeImportsMode.All); 2125 return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences, mode); 2126 } 2127 2128 function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { 2129 return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper); 2130 } 2131 2132 function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult>; 2133 function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult[]>; 2134 function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>; 2135 function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>; 2136 function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>; 2137 function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]> { 2138 const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[]; 2139 return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action); 2140 } 2141 2142 function applySingleCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult> { 2143 const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName); 2144 Debug.assertEqual(action.type, "install package"); 2145 return host.installPackage 2146 ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName }) 2147 : Promise.reject("Host does not implement `installPackage`"); 2148 } 2149 2150 function getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { 2151 return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position, options); 2152 } 2153 2154 function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { 2155 // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too 2156 // expensive to do during typing scenarios 2157 // i.e. whether we're dealing with: 2158 // var x = new foo<| ( with class foo<T>{} ) 2159 // or 2160 // var y = 3 <| 2161 if (openingBrace === CharacterCodes.lessThan) { 2162 return false; 2163 } 2164 2165 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2166 2167 // Check if in a context where we don't want to perform any insertion 2168 if (isInString(sourceFile, position)) { 2169 return false; 2170 } 2171 2172 if (isInsideJsxElementOrAttribute(sourceFile, position)) { 2173 return openingBrace === CharacterCodes.openBrace; 2174 } 2175 2176 if (isInTemplateString(sourceFile, position)) { 2177 return false; 2178 } 2179 2180 switch (openingBrace) { 2181 case CharacterCodes.singleQuote: 2182 case CharacterCodes.doubleQuote: 2183 case CharacterCodes.backtick: 2184 return !isInComment(sourceFile, position); 2185 } 2186 2187 return true; 2188 } 2189 2190 function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined { 2191 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2192 const token = findPrecedingToken(position, sourceFile); 2193 if (!token) return undefined; 2194 const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent 2195 : isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined; 2196 if (element && isUnclosedTag(element)) { 2197 return { newText: `</${element.openingElement.tagName.getText(sourceFile)}>` }; 2198 } 2199 const fragment = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningFragment(token.parent) ? token.parent.parent 2200 : isJsxText(token) && isJsxFragment(token.parent) ? token.parent : undefined; 2201 if (fragment && isUnclosedFragment(fragment)) { 2202 return { newText: "</>" }; 2203 } 2204 } 2205 2206 function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) { 2207 return { 2208 lineStarts: sourceFile.getLineStarts(), 2209 firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, 2210 lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line 2211 }; 2212 } 2213 2214 function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { 2215 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2216 const textChanges: TextChange[] = []; 2217 const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); 2218 2219 let isCommenting = insertComment || false; 2220 let leftMostPosition = Number.MAX_VALUE; 2221 const lineTextStarts = new Map<string, number>(); 2222 const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); 2223 const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); 2224 const openComment = isJsx ? "{/*" : "//"; 2225 2226 // Check each line before any text changes. 2227 for (let i = firstLine; i <= lastLine; i++) { 2228 const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); 2229 2230 // Find the start of text and the left-most character. No-op on empty lines. 2231 const regExec = firstNonWhitespaceCharacterRegex.exec(lineText); 2232 if (regExec) { 2233 leftMostPosition = Math.min(leftMostPosition, regExec.index); 2234 lineTextStarts.set(i.toString(), regExec.index); 2235 2236 if (lineText.substr(regExec.index, openComment.length) !== openComment) { 2237 isCommenting = insertComment === undefined || insertComment; 2238 } 2239 } 2240 } 2241 2242 // Push all text changes. 2243 for (let i = firstLine; i <= lastLine; i++) { 2244 // If the range is multiline and ends on a beginning of a line, don't comment/uncomment. 2245 if (firstLine !== lastLine && lineStarts[i] === textRange.end) { 2246 continue; 2247 } 2248 2249 const lineTextStart = lineTextStarts.get(i.toString()); 2250 2251 // If the line is not an empty line; otherwise no-op. 2252 if (lineTextStart !== undefined) { 2253 if (isJsx) { 2254 textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); 2255 } 2256 else if (isCommenting) { 2257 textChanges.push({ 2258 newText: openComment, 2259 span: { 2260 length: 0, 2261 start: lineStarts[i] + leftMostPosition 2262 } 2263 }); 2264 } 2265 else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { 2266 textChanges.push({ 2267 newText: "", 2268 span: { 2269 length: openComment.length, 2270 start: lineStarts[i] + lineTextStart 2271 } 2272 }); 2273 } 2274 } 2275 } 2276 2277 return textChanges; 2278 } 2279 2280 function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { 2281 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2282 const textChanges: TextChange[] = []; 2283 const { text } = sourceFile; 2284 2285 let hasComment = false; 2286 let isCommenting = insertComment || false; 2287 const positions = [] as number[] as SortedArray<number>; 2288 2289 let { pos } = textRange; 2290 const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); 2291 2292 const openMultiline = isJsx ? "{/*" : "/*"; 2293 const closeMultiline = isJsx ? "*/}" : "*/"; 2294 const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; 2295 const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; 2296 2297 // Get all comment positions 2298 while (pos <= textRange.end) { 2299 // Start of comment is considered inside comment. 2300 const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; 2301 const commentRange = isInComment(sourceFile, pos + offset); 2302 2303 // If position is in a comment add it to the positions array. 2304 if (commentRange) { 2305 // Comment range doesn't include the brace character. Increase it to include them. 2306 if (isJsx) { 2307 commentRange.pos--; 2308 commentRange.end++; 2309 } 2310 2311 positions.push(commentRange.pos); 2312 if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { 2313 positions.push(commentRange.end); 2314 } 2315 2316 hasComment = true; 2317 pos = commentRange.end + 1; 2318 } 2319 else { // If it's not in a comment range, then we need to comment the uncommented portions. 2320 const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); 2321 2322 isCommenting = insertComment !== undefined 2323 ? insertComment 2324 : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again. 2325 pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; 2326 } 2327 } 2328 2329 // If it didn't found a comment and isCommenting is false means is only empty space. 2330 // We want to insert comment in this scenario. 2331 if (isCommenting || !hasComment) { 2332 if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { 2333 insertSorted(positions, textRange.pos, compareValues); 2334 } 2335 insertSorted(positions, textRange.end, compareValues); 2336 2337 // Insert open comment if the first position is not a comment already. 2338 const firstPos = positions[0]; 2339 if (text.substr(firstPos, openMultiline.length) !== openMultiline) { 2340 textChanges.push({ 2341 newText: openMultiline, 2342 span: { 2343 length: 0, 2344 start: firstPos 2345 } 2346 }); 2347 } 2348 2349 // Insert open and close comment to all positions between first and last. Exclusive. 2350 for (let i = 1; i < positions.length - 1; i++) { 2351 if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { 2352 textChanges.push({ 2353 newText: closeMultiline, 2354 span: { 2355 length: 0, 2356 start: positions[i] 2357 } 2358 }); 2359 } 2360 2361 if (text.substr(positions[i], openMultiline.length) !== openMultiline) { 2362 textChanges.push({ 2363 newText: openMultiline, 2364 span: { 2365 length: 0, 2366 start: positions[i] 2367 } 2368 }); 2369 } 2370 } 2371 2372 // Insert open comment if the last position is not a comment already. 2373 if (textChanges.length % 2 !== 0) { 2374 textChanges.push({ 2375 newText: closeMultiline, 2376 span: { 2377 length: 0, 2378 start: positions[positions.length - 1] 2379 } 2380 }); 2381 } 2382 } 2383 else { 2384 // If is not commenting then remove all comments found. 2385 for (const pos of positions) { 2386 const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0; 2387 const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; 2388 textChanges.push({ 2389 newText: "", 2390 span: { 2391 length: openMultiline.length, 2392 start: pos - offset 2393 } 2394 }); 2395 } 2396 } 2397 2398 return textChanges; 2399 } 2400 2401 function commentSelection(fileName: string, textRange: TextRange): TextChange[] { 2402 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2403 const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange); 2404 2405 // If there is a selection that is on the same line, add multiline. 2406 return firstLine === lastLine && textRange.pos !== textRange.end 2407 ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true) 2408 : toggleLineComment(fileName, textRange, /*insertComment*/ true); 2409 } 2410 2411 function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { 2412 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2413 const textChanges: TextChange[] = []; 2414 const { pos } = textRange; 2415 let { end } = textRange; 2416 2417 // If cursor is not a selection we need to increase the end position 2418 // to include the start of the comment. 2419 if (pos === end) { 2420 end += isInsideJsxElement(sourceFile, pos) ? 2 : 1; 2421 } 2422 2423 for (let i = pos; i <= end; i++) { 2424 const commentRange = isInComment(sourceFile, i); 2425 if (commentRange) { 2426 switch (commentRange.kind) { 2427 case SyntaxKind.SingleLineCommentTrivia: 2428 textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); 2429 break; 2430 case SyntaxKind.MultiLineCommentTrivia: 2431 textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); 2432 } 2433 2434 i = commentRange.end + 1; 2435 } 2436 } 2437 2438 return textChanges; 2439 } 2440 2441 function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { 2442 return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || 2443 isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); 2444 } 2445 2446 function isUnclosedFragment({ closingFragment, parent }: JsxFragment): boolean { 2447 return !!(closingFragment.flags & NodeFlags.ThisNodeHasError) || (isJsxFragment(parent) && isUnclosedFragment(parent)); 2448 } 2449 2450 function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { 2451 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2452 const range = formatting.getRangeOfEnclosingComment(sourceFile, position); 2453 return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined; 2454 } 2455 2456 function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { 2457 // Note: while getting todo comments seems like a syntactic operation, we actually 2458 // treat it as a semantic operation here. This is because we expect our host to call 2459 // this on every single file. If we treat this syntactically, then that will cause 2460 // us to populate and throw away the tree in our syntax tree cache for each file. By 2461 // treating this as a semantic operation, we can access any tree without throwing 2462 // anything away. 2463 synchronizeHostData(); 2464 2465 const sourceFile = getValidSourceFile(fileName); 2466 2467 cancellationToken.throwIfCancellationRequested(); 2468 2469 const fileContents = sourceFile.text; 2470 const result: TodoComment[] = []; 2471 2472 // Exclude node_modules or oh_modules files as we don't want to show the todos of external libraries. 2473 if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName) && !isOHModulesFile(sourceFile.fileName)) { 2474 const regExp = getTodoCommentsRegExp(); 2475 2476 let matchArray: RegExpExecArray | null; 2477 while (matchArray = regExp.exec(fileContents)) { 2478 cancellationToken.throwIfCancellationRequested(); 2479 2480 // If we got a match, here is what the match array will look like. Say the source text is: 2481 // 2482 // " // hack 1" 2483 // 2484 // The result array with the regexp: will be: 2485 // 2486 // ["// hack 1", "// ", "hack 1", undefined, "hack"] 2487 // 2488 // Here are the relevant capture groups: 2489 // 0) The full match for the entire regexp. 2490 // 1) The preamble to the message portion. 2491 // 2) The message portion. 2492 // 3...N) The descriptor that was matched - by index. 'undefined' for each 2493 // descriptor that didn't match. an actual value if it did match. 2494 // 2495 // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. 2496 // "hack" in position 4 means HACK did match. 2497 const firstDescriptorCaptureIndex = 3; 2498 Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex); 2499 2500 const preamble = matchArray[1]; 2501 const matchPosition = matchArray.index + preamble.length; 2502 2503 // OK, we have found a match in the file. This is only an acceptable match if 2504 // it is contained within a comment. 2505 if (!isInComment(sourceFile, matchPosition)) { 2506 continue; 2507 } 2508 2509 let descriptor: TodoCommentDescriptor | undefined; 2510 for (let i = 0; i < descriptors.length; i++) { 2511 if (matchArray[i + firstDescriptorCaptureIndex]) { 2512 descriptor = descriptors[i]; 2513 } 2514 } 2515 if (descriptor === undefined) return Debug.fail(); 2516 2517 // We don't want to match something like 'TODOBY', so we make sure a non 2518 // letter/digit follows the match. 2519 if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { 2520 continue; 2521 } 2522 2523 const message = matchArray[2]; 2524 result.push({ descriptor, message, position: matchPosition }); 2525 } 2526 } 2527 2528 return result; 2529 2530 function escapeRegExp(str: string): string { 2531 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 2532 } 2533 2534 function getTodoCommentsRegExp(): RegExp { 2535 // NOTE: `?:` means 'non-capture group'. It allows us to have groups without having to 2536 // filter them out later in the final result array. 2537 2538 // TODO comments can appear in one of the following forms: 2539 // 2540 // 1) // TODO or /////////// TODO 2541 // 2542 // 2) /* TODO or /********** TODO 2543 // 2544 // 3) /* 2545 // * TODO 2546 // */ 2547 // 2548 // The following three regexps are used to match the start of the text up to the TODO 2549 // comment portion. 2550 const singleLineCommentStart = /(?:\/\/+\s*)/.source; 2551 const multiLineCommentStart = /(?:\/\*+\s*)/.source; 2552 const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source; 2553 2554 // Match any of the above three TODO comment start regexps. 2555 // Note that the outermost group *is* a capture group. We want to capture the preamble 2556 // so that we can determine the starting position of the TODO comment match. 2557 const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")"; 2558 2559 // Takes the descriptors and forms a regexp that matches them as if they were literals. 2560 // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be: 2561 // 2562 // (?:(TODO\(jason\))|(HACK)) 2563 // 2564 // Note that the outermost group is *not* a capture group, but the innermost groups 2565 // *are* capture groups. By capturing the inner literals we can determine after 2566 // matching which descriptor we are dealing with. 2567 const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")"; 2568 2569 // After matching a descriptor literal, the following regexp matches the rest of the 2570 // text up to the end of the line (or */). 2571 const endOfLineOrEndOfComment = /(?:$|\*\/)/.source; 2572 const messageRemainder = /(?:.*?)/.source; 2573 2574 // This is the portion of the match we'll return as part of the TODO comment result. We 2575 // match the literal portion up to the end of the line or end of comment. 2576 const messagePortion = "(" + literals + messageRemainder + ")"; 2577 const regExpString = preamble + messagePortion + endOfLineOrEndOfComment; 2578 2579 // The final regexp will look like this: 2580 // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim 2581 2582 // The flags of the regexp are important here. 2583 // 'g' is so that we are doing a global search and can find matches several times 2584 // in the input. 2585 // 2586 // 'i' is for case insensitivity (We do this to match C# TODO comment code). 2587 // 2588 // 'm' is so we can find matches in a multi-line input. 2589 return new RegExp(regExpString, "gim"); 2590 } 2591 2592 function isLetterOrDigit(char: number): boolean { 2593 return (char >= CharacterCodes.a && char <= CharacterCodes.z) || 2594 (char >= CharacterCodes.A && char <= CharacterCodes.Z) || 2595 (char >= CharacterCodes._0 && char <= CharacterCodes._9); 2596 } 2597 2598 function isNodeModulesFile(path: string): boolean { 2599 return stringContains(path, "/node_modules/"); 2600 } 2601 2602 function isOHModulesFile(path: string): boolean { 2603 return stringContains(path, "/oh_modules/"); 2604 } 2605 } 2606 2607 function getRenameInfo(fileName: string, position: number, preferences: UserPreferences | RenameInfoOptions | undefined): RenameInfo { 2608 synchronizeHostData(); 2609 return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, preferences || {}); 2610 } 2611 2612 function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext { 2613 const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end]; 2614 return { 2615 file, 2616 startPosition, 2617 endPosition, 2618 program: getProgram()!, 2619 host, 2620 formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217 2621 cancellationToken, 2622 preferences, 2623 triggerReason, 2624 kind 2625 }; 2626 } 2627 2628 function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext { 2629 return { 2630 file, 2631 program: getProgram()!, 2632 host, 2633 span, 2634 preferences, 2635 cancellationToken, 2636 }; 2637 } 2638 2639 function getSmartSelectionRange(fileName: string, position: number): SelectionRange { 2640 return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); 2641 } 2642 2643 function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { 2644 synchronizeHostData(); 2645 const file = getValidSourceFile(fileName); 2646 return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); 2647 } 2648 2649 function getEditsForRefactor( 2650 fileName: string, 2651 formatOptions: FormatCodeSettings, 2652 positionOrRange: number | TextRange, 2653 refactorName: string, 2654 actionName: string, 2655 preferences: UserPreferences = emptyOptions, 2656 ): RefactorEditInfo | undefined { 2657 synchronizeHostData(); 2658 const file = getValidSourceFile(fileName); 2659 return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); 2660 } 2661 2662 function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { 2663 // Go to Definition supports returning a zero-length span at position 0 for 2664 // non-existent files. We need to special-case the conversion of position 0 2665 // to avoid a crash trying to get the text for that file, since this function 2666 // otherwise assumes that 'fileName' is the name of a file that exists. 2667 if (position === 0) { 2668 return { line: 0, character: 0 }; 2669 } 2670 return sourceMapper.toLineColumnOffset(fileName, position); 2671 } 2672 2673 function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { 2674 synchronizeHostData(); 2675 const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); 2676 return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration)); 2677 } 2678 2679 function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { 2680 synchronizeHostData(); 2681 const sourceFile = getValidSourceFile(fileName); 2682 const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); 2683 return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; 2684 } 2685 2686 function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { 2687 synchronizeHostData(); 2688 const sourceFile = getValidSourceFile(fileName); 2689 const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); 2690 return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; 2691 } 2692 2693 function provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences = emptyOptions): InlayHint[] { 2694 synchronizeHostData(); 2695 const sourceFile = getValidSourceFile(fileName); 2696 return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); 2697 } 2698 2699 function updateRootFiles(rootFiles: string[]) { 2700 host.getScriptFileNames = () => rootFiles 2701 } 2702 2703 function getProps(): string[] { 2704 return host.uiProps ? host.uiProps : []; 2705 } 2706 2707 const ls: LanguageService = { 2708 dispose, 2709 cleanupSemanticCache, 2710 getSyntacticDiagnostics, 2711 getSemanticDiagnostics, 2712 getSuggestionDiagnostics, 2713 getCompilerOptionsDiagnostics, 2714 getSyntacticClassifications, 2715 getSemanticClassifications, 2716 getEncodedSyntacticClassifications, 2717 getEncodedSemanticClassifications, 2718 getCompletionsAtPosition, 2719 getCompletionEntryDetails, 2720 getCompletionEntrySymbol, 2721 getSignatureHelpItems, 2722 getQuickInfoAtPosition, 2723 getDefinitionAtPosition, 2724 getDefinitionAndBoundSpan, 2725 getImplementationAtPosition, 2726 getTypeDefinitionAtPosition, 2727 getReferencesAtPosition, 2728 findReferences, 2729 getFileReferences, 2730 getOccurrencesAtPosition, 2731 getDocumentHighlights, 2732 getNameOrDottedNameSpan, 2733 getBreakpointStatementAtPosition, 2734 getNavigateToItems, 2735 getRenameInfo, 2736 getSmartSelectionRange, 2737 findRenameLocations, 2738 getNavigationBarItems, 2739 getNavigationTree, 2740 getOutliningSpans, 2741 getTodoComments, 2742 getBraceMatchingAtPosition, 2743 getIndentationAtPosition, 2744 getFormattingEditsForRange, 2745 getFormattingEditsForDocument, 2746 getFormattingEditsAfterKeystroke, 2747 getDocCommentTemplateAtPosition, 2748 isValidBraceCompletionAtPosition, 2749 getJsxClosingTagAtPosition, 2750 getSpanOfEnclosingComment, 2751 getCodeFixesAtPosition, 2752 getCombinedCodeFix, 2753 applyCodeActionCommand, 2754 organizeImports, 2755 getEditsForFileRename, 2756 getEmitOutput, 2757 getNonBoundSourceFile, 2758 getProgram, 2759 getBuilderProgram, 2760 getCurrentProgram: () => program, 2761 getAutoImportProvider, 2762 updateIsDefinitionOfReferencedSymbols, 2763 getApplicableRefactors, 2764 getEditsForRefactor, 2765 toLineColumnOffset, 2766 getSourceMapper: () => sourceMapper, 2767 clearSourceMapperCache: () => sourceMapper.clearCache(), 2768 prepareCallHierarchy, 2769 provideCallHierarchyIncomingCalls, 2770 provideCallHierarchyOutgoingCalls, 2771 toggleLineComment, 2772 toggleMultilineComment, 2773 commentSelection, 2774 uncommentSelection, 2775 provideInlayHints, 2776 updateRootFiles, 2777 getProps 2778 }; 2779 2780 switch (languageServiceMode) { 2781 case LanguageServiceMode.Semantic: 2782 break; 2783 case LanguageServiceMode.PartialSemantic: 2784 invalidOperationsInPartialSemanticMode.forEach(key => 2785 ls[key] = () => { 2786 throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.PartialSemantic`); 2787 } 2788 ); 2789 break; 2790 case LanguageServiceMode.Syntactic: 2791 invalidOperationsInSyntacticMode.forEach(key => 2792 ls[key] = () => { 2793 throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.Syntactic`); 2794 } 2795 ); 2796 break; 2797 default: 2798 Debug.assertNever(languageServiceMode); 2799 } 2800 return ls; 2801 } 2802 2803 /* @internal */ 2804 /** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */ 2805 export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap<number> { 2806 if (!sourceFile.nameTable) { 2807 initializeNameTable(sourceFile); 2808 } 2809 2810 return sourceFile.nameTable!; // TODO: GH#18217 2811 } 2812 2813 function initializeNameTable(sourceFile: SourceFile): void { 2814 const nameTable = sourceFile.nameTable = new Map(); 2815 sourceFile.forEachChild(function walk(node) { 2816 if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) { 2817 const text = getEscapedTextOfIdentifierOrLiteral(node); 2818 nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); 2819 } 2820 else if (isPrivateIdentifier(node)) { 2821 const text = node.escapedText; 2822 nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); 2823 } 2824 2825 forEachChild(node, walk); 2826 if (hasJSDocNodes(node)) { 2827 for (const jsDoc of node.jsDoc!) { 2828 forEachChild(jsDoc, walk); 2829 } 2830 } 2831 }); 2832 } 2833 2834 /** 2835 * We want to store any numbers/strings if they were a name that could be 2836 * related to a declaration. So, if we have 'import x = require("something")' 2837 * then we want 'something' to be in the name table. Similarly, if we have 2838 * "a['propname']" then we want to store "propname" in the name table. 2839 */ 2840 function literalIsName(node: StringLiteralLike | NumericLiteral): boolean { 2841 return isDeclarationName(node) || 2842 node.parent.kind === SyntaxKind.ExternalModuleReference || 2843 isArgumentOfElementAccessExpression(node) || 2844 isLiteralComputedPropertyDeclarationName(node); 2845 } 2846 2847 /** 2848 * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } 2849 */ 2850 /* @internal */ 2851 export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined { 2852 const element = getContainingObjectLiteralElementWorker(node); 2853 return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined; 2854 } 2855 function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined { 2856 switch (node.kind) { 2857 case SyntaxKind.StringLiteral: 2858 case SyntaxKind.NoSubstitutionTemplateLiteral: 2859 case SyntaxKind.NumericLiteral: 2860 if (node.parent.kind === SyntaxKind.ComputedPropertyName) { 2861 return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; 2862 } 2863 // falls through 2864 2865 case SyntaxKind.Identifier: 2866 return isObjectLiteralElement(node.parent) && 2867 (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) && 2868 node.parent.name === node ? node.parent : undefined; 2869 } 2870 return undefined; 2871 } 2872 2873 /* @internal */ 2874 export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes }; 2875 2876 function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined { 2877 const object = getContainingObjectLiteralElement(node); 2878 if (object) { 2879 const contextualType = checker.getContextualType(object.parent); 2880 const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false); 2881 if (properties && properties.length === 1) { 2882 return first(properties); 2883 } 2884 } 2885 return checker.getSymbolAtLocation(node); 2886 } 2887 2888 /** Gets all symbols for one property. Does not get symbols for every property. */ 2889 /* @internal */ 2890 export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] { 2891 const name = getNameFromPropertyName(node.name); 2892 if (!name) return emptyArray; 2893 if (!contextualType.isUnion()) { 2894 const symbol = contextualType.getProperty(name); 2895 return symbol ? [symbol] : emptyArray; 2896 } 2897 2898 const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); 2899 if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) { 2900 const symbol = contextualType.getProperty(name); 2901 if (symbol) return [symbol]; 2902 } 2903 if (discriminatedPropertySymbols.length === 0) { 2904 // Bad discriminant -- do again without discriminating 2905 return mapDefined(contextualType.types, t => t.getProperty(name)); 2906 } 2907 return discriminatedPropertySymbols; 2908 } 2909 2910 function isArgumentOfElementAccessExpression(node: Node) { 2911 return node && 2912 node.parent && 2913 node.parent.kind === SyntaxKind.ElementAccessExpression && 2914 (node.parent as ElementAccessExpression).argumentExpression === node; 2915 } 2916 2917 /// getDefaultLibraryFilePath 2918 declare const __dirname: string; 2919 2920 /** 2921 * Get the path of the default library files (lib.d.ts) as distributed with the typescript 2922 * node package. 2923 * The functionality is not supported if the ts module is consumed outside of a node module. 2924 */ 2925 export function getDefaultLibFilePath(options: CompilerOptions): string { 2926 // Check __dirname is defined and that we are on a node.js system. 2927 if (typeof __dirname !== "undefined") { 2928 return combinePaths(__dirname, getDefaultLibFileName(options)); 2929 } 2930 2931 throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. "); 2932 } 2933 2934 setObjectAllocator(getServicesObjectAllocator()); 2935} 2936