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): 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 builderProgram = createIncrementalProgram(options); 1416 program = builderProgram.getProgram(); 1417 } else { 1418 program = createProgram(options); 1419 } 1420 1421 // 'getOrCreateSourceFile' depends on caching but should be used past this point. 1422 // After this point, the cache needs to be cleared to allow all collected snapshots to be released 1423 compilerHost = undefined; 1424 parsedCommandLines = undefined; 1425 1426 // 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, 1427 // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during 1428 // the course of whatever called `synchronizeHostData` 1429 sourceMapper.clearCache(); 1430 1431 // Make sure all the nodes in the program are both bound, and have their parent 1432 // pointers set property. 1433 program.getTypeChecker(); 1434 return; 1435 1436 function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined { 1437 const path = toPath(fileName, currentDirectory, getCanonicalFileName); 1438 const existing = parsedCommandLines?.get(path); 1439 if (existing !== undefined) return existing || undefined; 1440 1441 const result = host.getParsedCommandLine ? 1442 host.getParsedCommandLine(fileName) : 1443 getParsedCommandLineOfConfigFileUsingSourceFile(fileName); 1444 (parsedCommandLines ||= new Map()).set(path, result || false); 1445 return result; 1446 } 1447 1448 function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined { 1449 const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined; 1450 if (!result) return undefined; 1451 result.path = toPath(configFileName, currentDirectory, getCanonicalFileName); 1452 result.resolvedPath = result.path; 1453 result.originalFileName = result.fileName; 1454 return parseJsonSourceFileConfigFileContent( 1455 result, 1456 parseConfigHost, 1457 getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), 1458 /*optionsToExtend*/ undefined, 1459 getNormalizedAbsolutePath(configFileName, currentDirectory), 1460 ); 1461 } 1462 1463 function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) { 1464 if (host.getParsedCommandLine) { 1465 host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions); 1466 } 1467 else if (oldResolvedRef) { 1468 onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions); 1469 } 1470 } 1471 1472 // Release any files we have acquired in the old program but are 1473 // not part of the new program. 1474 function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { 1475 const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); 1476 documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); 1477 } 1478 1479 function getOrCreateSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { 1480 return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersionOrOptions, onError, shouldCreateNewSourceFile); 1481 } 1482 1483 function getOrCreateSourceFileByPath(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { 1484 Debug.assert(compilerHost, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host."); 1485 // The program is asking for this file, check first if the host can locate it. 1486 // If the host can not locate the file, then it does not exist. return undefined 1487 // to the program to allow reporting of errors for missing files. 1488 const scriptSnapshot = host.getScriptSnapshot(fileName); 1489 if (!scriptSnapshot) { 1490 return undefined; 1491 } 1492 1493 const scriptKind = getScriptKind(fileName, host); 1494 const scriptVersion = host.getScriptVersion(fileName); 1495 1496 // Check if the language version has changed since we last created a program; if they are the same, 1497 // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile 1498 // can not be reused. we have to dump all syntax trees and create new ones. 1499 if (!shouldCreateNewSourceFile) { 1500 // Check if the old program had this file already 1501 const oldSourceFile = program && program.getSourceFileByPath(path); 1502 if (oldSourceFile) { 1503 // We already had a source file for this file name. Go to the registry to 1504 // ensure that we get the right up to date version of it. We need this to 1505 // address the following race-condition. Specifically, say we have the following: 1506 // 1507 // LS1 1508 // \ 1509 // DocumentRegistry 1510 // / 1511 // LS2 1512 // 1513 // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates 1514 // it's version of 'foo.ts' to version 2. This will cause LS2 and the 1515 // DocumentRegistry to have version 2 of the document. However, LS1 will 1516 // have version 1. And *importantly* this source file will be *corrupt*. 1517 // The act of creating version 2 of the file irrevocably damages the version 1518 // 1 file. 1519 // 1520 // So, later when we call into LS1, we need to make sure that it doesn't use 1521 // it's source file any more, and instead defers to DocumentRegistry to get 1522 // either version 1, version 2 (or some other version) depending on what the 1523 // host says should be used. 1524 1525 // We do not support the scenario where a host can modify a registered 1526 // file's script kind, i.e. in one project some file is treated as ".ts" 1527 // and in another as ".js" 1528 if (scriptKind === oldSourceFile.scriptKind) { 1529 return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); 1530 } 1531 else { 1532 // Release old source file and fall through to aquire new file with new script kind 1533 documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); 1534 } 1535 } 1536 1537 // We didn't already have the file. Fall through and acquire it from the registry. 1538 } 1539 1540 // Could not find this file in the old program, create a new SourceFile for it. 1541 return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); 1542 } 1543 } 1544 1545 // TODO: GH#18217 frequently asserted as defined 1546 function getProgram(): Program | undefined { 1547 if (languageServiceMode === LanguageServiceMode.Syntactic) { 1548 Debug.assert(program === undefined); 1549 return undefined; 1550 } 1551 1552 synchronizeHostData(); 1553 1554 return program; 1555 } 1556 1557 function getBuilderProgram(): BuilderProgram | undefined { 1558 if (languageServiceMode === LanguageServiceMode.Syntactic) { 1559 Debug.assert(builderProgram === undefined); 1560 return undefined; 1561 } 1562 1563 synchronizeHostData(isIncrementalCompilation(host.getCompilationSettings())); 1564 1565 return builderProgram; 1566 } 1567 1568 function getAutoImportProvider(): Program | undefined { 1569 return host.getPackageJsonAutoImportProvider?.(); 1570 } 1571 1572 function updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set<DocumentSpan>): boolean { 1573 const checker = program.getTypeChecker(); 1574 const symbol = getSymbolForProgram(); 1575 1576 if (!symbol) return false; 1577 1578 for (const referencedSymbol of referencedSymbols) { 1579 for (const ref of referencedSymbol.references) { 1580 const refNode = getNodeForSpan(ref); 1581 Debug.assertIsDefined(refNode); 1582 if (knownSymbolSpans.has(ref) || FindAllReferences.isDeclarationOfSymbol(refNode, symbol)) { 1583 knownSymbolSpans.add(ref); 1584 ref.isDefinition = true; 1585 const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); 1586 if (mappedSpan) { 1587 knownSymbolSpans.add(mappedSpan); 1588 } 1589 } 1590 else { 1591 ref.isDefinition = false; 1592 } 1593 } 1594 } 1595 1596 return true; 1597 1598 function getSymbolForProgram(): Symbol | undefined { 1599 for (const referencedSymbol of referencedSymbols) { 1600 for (const ref of referencedSymbol.references) { 1601 if (knownSymbolSpans.has(ref)) { 1602 const refNode = getNodeForSpan(ref); 1603 Debug.assertIsDefined(refNode); 1604 return checker.getSymbolAtLocation(refNode); 1605 } 1606 const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); 1607 if (mappedSpan && knownSymbolSpans.has(mappedSpan)) { 1608 const refNode = getNodeForSpan(mappedSpan); 1609 if (refNode) { 1610 return checker.getSymbolAtLocation(refNode); 1611 } 1612 } 1613 } 1614 } 1615 1616 return undefined; 1617 } 1618 1619 function getNodeForSpan(docSpan: DocumentSpan): Node | undefined { 1620 const sourceFile = program.getSourceFile(docSpan.fileName); 1621 if (!sourceFile) return undefined; 1622 const rawNode = getTouchingPropertyName(sourceFile, docSpan.textSpan.start); 1623 const adjustedNode = FindAllReferences.Core.getAdjustedNode(rawNode, { use: FindAllReferences.FindReferencesUse.References }); 1624 return adjustedNode; 1625 } 1626 } 1627 1628 function cleanupSemanticCache(): void { 1629 program = undefined!; // TODO: GH#18217 1630 } 1631 1632 function dispose(): void { 1633 if (program) { 1634 // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host 1635 const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()); 1636 forEach(program.getSourceFiles(), f => 1637 documentRegistry.releaseDocumentWithKey(f.resolvedPath, key, f.scriptKind, f.impliedNodeFormat)); 1638 program = undefined!; // TODO: GH#18217 1639 } 1640 host = undefined!; 1641 } 1642 1643 /// Diagnostics 1644 function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] { 1645 synchronizeHostData(); 1646 1647 return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice(); 1648 } 1649 1650 /** 1651 * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors 1652 * If '-d' enabled, report both semantic and emitter errors 1653 */ 1654 function getSemanticDiagnostics(fileName: string): Diagnostic[] { 1655 synchronizeHostData(); 1656 1657 const targetSourceFile = getValidSourceFile(fileName); 1658 1659 // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file. 1660 // Therefore only get diagnostics for given file. 1661 1662 const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken); 1663 if (!getEmitDeclarations(program.getCompilerOptions())) { 1664 return semanticDiagnostics.slice(); 1665 } 1666 1667 // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface 1668 const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken); 1669 return [...semanticDiagnostics, ...declarationDiagnostics]; 1670 } 1671 1672 function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] { 1673 synchronizeHostData(); 1674 return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken); 1675 } 1676 1677 function getCompilerOptionsDiagnostics() { 1678 synchronizeHostData(); 1679 return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; 1680 } 1681 1682 function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions, formattingSettings?: FormatCodeSettings): CompletionInfo | undefined { 1683 // Convert from deprecated options names to new names 1684 const fullPreferences: UserPreferences = { 1685 ...identity<UserPreferences>(options), // avoid excess property check 1686 includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports, 1687 includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions, 1688 }; 1689 synchronizeHostData(); 1690 return Completions.getCompletionsAtPosition( 1691 host, 1692 program, 1693 log, 1694 getValidSourceFile(fileName), 1695 position, 1696 fullPreferences, 1697 options.triggerCharacter, 1698 options.triggerKind, 1699 cancellationToken, 1700 formattingSettings && formatting.getFormatContext(formattingSettings, host)); 1701 } 1702 1703 function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { 1704 synchronizeHostData(); 1705 return Completions.getCompletionEntryDetails( 1706 program, 1707 log, 1708 getValidSourceFile(fileName), 1709 position, 1710 { name, source, data }, 1711 host, 1712 (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217 1713 preferences, 1714 cancellationToken, 1715 ); 1716 } 1717 1718 function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined { 1719 synchronizeHostData(); 1720 return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences); 1721 } 1722 1723 function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined { 1724 synchronizeHostData(); 1725 1726 const sourceFile = getValidSourceFile(fileName); 1727 const node = getTouchingPropertyName(sourceFile, position); 1728 if (node === sourceFile) { 1729 // Avoid giving quickInfo for the sourceFile as a whole. 1730 return undefined; 1731 } 1732 1733 const typeChecker = program.getTypeChecker(); 1734 const nodeForQuickInfo = getNodeForQuickInfo(node); 1735 const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker); 1736 1737 if (!symbol || typeChecker.isUnknownSymbol(symbol)) { 1738 const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined; 1739 return type && { 1740 kind: ScriptElementKind.unknown, 1741 kindModifiers: ScriptElementKindModifier.none, 1742 textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), 1743 displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))), 1744 documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined, 1745 tags: type.symbol ? type.symbol.getJsDocTags(typeChecker) : undefined 1746 }; 1747 } 1748 1749 const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker => 1750 SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo) 1751 ); 1752 return { 1753 kind: symbolKind, 1754 kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), 1755 textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), 1756 displayParts, 1757 documentation, 1758 tags, 1759 }; 1760 } 1761 1762 function getNodeForQuickInfo(node: Node): Node { 1763 if (isNewExpression(node.parent) && node.pos === node.parent.pos) { 1764 return node.parent.expression; 1765 } 1766 if (isNamedTupleMember(node.parent) && node.pos === node.parent.pos) { 1767 return node.parent; 1768 } 1769 if (isImportMeta(node.parent) && node.parent.name === node) { 1770 return node.parent; 1771 } 1772 return node; 1773 } 1774 1775 function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean { 1776 switch (node.kind) { 1777 case SyntaxKind.Identifier: 1778 return !isLabelName(node) && !isTagName(node) && !isConstTypeReference(node.parent); 1779 case SyntaxKind.PropertyAccessExpression: 1780 case SyntaxKind.QualifiedName: 1781 // Don't return quickInfo if inside the comment in `a/**/.b` 1782 return !isInComment(sourceFile, position); 1783 case SyntaxKind.ThisKeyword: 1784 case SyntaxKind.ThisType: 1785 case SyntaxKind.SuperKeyword: 1786 case SyntaxKind.NamedTupleMember: 1787 return true; 1788 case SyntaxKind.MetaProperty: 1789 return isImportMeta(node); 1790 default: 1791 return false; 1792 } 1793 } 1794 1795 /// Goto definition 1796 function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined { 1797 synchronizeHostData(); 1798 return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly, stopAtAlias); 1799 } 1800 1801 function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined { 1802 synchronizeHostData(); 1803 return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position); 1804 } 1805 1806 function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { 1807 synchronizeHostData(); 1808 return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); 1809 } 1810 1811 /// Goto implementation 1812 1813 function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { 1814 synchronizeHostData(); 1815 return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); 1816 } 1817 1818 /// References and Occurrences 1819 function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined { 1820 return flatMap( 1821 getDocumentHighlights(fileName, position, [fileName]), 1822 entry => entry.highlightSpans.map<ReferenceEntry>(highlightSpan => ({ 1823 fileName: entry.fileName, 1824 textSpan: highlightSpan.textSpan, 1825 isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference, 1826 ...highlightSpan.isInString && { isInString: true }, 1827 ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan } 1828 })) 1829 ); 1830 } 1831 1832 function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined { 1833 const normalizedFileName = normalizePath(fileName); 1834 Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName)); 1835 synchronizeHostData(); 1836 const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName)); 1837 const sourceFile = getValidSourceFile(fileName); 1838 return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch); 1839 } 1840 1841 function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined { 1842 synchronizeHostData(); 1843 const sourceFile = getValidSourceFile(fileName); 1844 const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); 1845 if (!Rename.nodeIsEligibleForRename(node)) return undefined; 1846 if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) { 1847 const { openingElement, closingElement } = node.parent.parent; 1848 return [openingElement, closingElement].map((node): RenameLocation => { 1849 const textSpan = createTextSpanFromNode(node.tagName, sourceFile); 1850 return { 1851 fileName: sourceFile.fileName, 1852 textSpan, 1853 ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent) 1854 }; 1855 }); 1856 } 1857 else { 1858 return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename }, 1859 (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false)); 1860 } 1861 } 1862 1863 function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { 1864 synchronizeHostData(); 1865 return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, FindAllReferences.toReferenceEntry); 1866 } 1867 1868 function getReferencesWorker<T>(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry<T>): T[] | undefined { 1869 synchronizeHostData(); 1870 1871 // Exclude default library when renaming as commonly user don't want to change that file. 1872 const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename 1873 ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) 1874 : program.getSourceFiles(); 1875 1876 return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); 1877 } 1878 1879 function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { 1880 synchronizeHostData(); 1881 return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); 1882 } 1883 1884 function getFileReferences(fileName: string): ReferenceEntry[] { 1885 synchronizeHostData(); 1886 return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(FindAllReferences.toReferenceEntry); 1887 } 1888 1889 function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] { 1890 synchronizeHostData(); 1891 const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles(); 1892 return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); 1893 } 1894 1895 function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) { 1896 synchronizeHostData(); 1897 1898 const sourceFile = getValidSourceFile(fileName); 1899 const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); 1900 return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit); 1901 } 1902 1903 // Signature help 1904 /** 1905 * This is a semantic operation. 1906 */ 1907 function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined { 1908 synchronizeHostData(); 1909 1910 const sourceFile = getValidSourceFile(fileName); 1911 1912 return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken); 1913 } 1914 1915 /// Syntactic features 1916 function getNonBoundSourceFile(fileName: string): SourceFile { 1917 return syntaxTreeCache.getCurrentSourceFile(fileName); 1918 } 1919 1920 function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined { 1921 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 1922 1923 // Get node at the location 1924 const node = getTouchingPropertyName(sourceFile, startPos); 1925 1926 if (node === sourceFile) { 1927 return undefined; 1928 } 1929 1930 switch (node.kind) { 1931 case SyntaxKind.PropertyAccessExpression: 1932 case SyntaxKind.QualifiedName: 1933 case SyntaxKind.StringLiteral: 1934 case SyntaxKind.FalseKeyword: 1935 case SyntaxKind.TrueKeyword: 1936 case SyntaxKind.NullKeyword: 1937 case SyntaxKind.SuperKeyword: 1938 case SyntaxKind.ThisKeyword: 1939 case SyntaxKind.ThisType: 1940 case SyntaxKind.Identifier: 1941 break; 1942 1943 // Cant create the text span 1944 default: 1945 return undefined; 1946 } 1947 1948 let nodeForStartPos = node; 1949 while (true) { 1950 if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) { 1951 // 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 1952 nodeForStartPos = nodeForStartPos.parent; 1953 } 1954 else if (isNameOfModuleDeclaration(nodeForStartPos)) { 1955 // If this is name of a module declarations, check if this is right side of dotted module name 1956 // 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 1957 // Then this name is name from dotted module 1958 if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration && 1959 (nodeForStartPos.parent.parent as ModuleDeclaration).body === nodeForStartPos.parent) { 1960 // Use parent module declarations name for start pos 1961 nodeForStartPos = (nodeForStartPos.parent.parent as ModuleDeclaration).name; 1962 } 1963 else { 1964 // We have to use this name for start pos 1965 break; 1966 } 1967 } 1968 else { 1969 // Is not a member expression so we have found the node for start pos 1970 break; 1971 } 1972 } 1973 1974 return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd()); 1975 } 1976 1977 function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined { 1978 // doesn't use compiler - no need to synchronize with host 1979 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 1980 1981 return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position); 1982 } 1983 1984 function getNavigationBarItems(fileName: string): NavigationBarItem[] { 1985 return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); 1986 } 1987 1988 function getNavigationTree(fileName: string): NavigationTree { 1989 return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); 1990 } 1991 1992 function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; 1993 function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] { 1994 synchronizeHostData(); 1995 1996 const responseFormat = format || SemanticClassificationFormat.Original; 1997 if (responseFormat === SemanticClassificationFormat.TwentyTwenty) { 1998 return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); 1999 } 2000 else { 2001 return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); 2002 } 2003 } 2004 2005 function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications { 2006 synchronizeHostData(); 2007 2008 const responseFormat = format || SemanticClassificationFormat.Original; 2009 if (responseFormat === SemanticClassificationFormat.Original) { 2010 return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); 2011 } 2012 else { 2013 return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); 2014 } 2015 } 2016 2017 function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { 2018 // doesn't use compiler - no need to synchronize with host 2019 return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); 2020 } 2021 2022 function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications { 2023 // doesn't use compiler - no need to synchronize with host 2024 return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); 2025 } 2026 2027 function getOutliningSpans(fileName: string): OutliningSpan[] { 2028 // doesn't use compiler - no need to synchronize with host 2029 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2030 return OutliningElementsCollector.collectElements(sourceFile, cancellationToken); 2031 } 2032 2033 const braceMatching = new Map(getEntries({ 2034 [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken, 2035 [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken, 2036 [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken, 2037 [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken, 2038 })); 2039 braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind)); 2040 2041 function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { 2042 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2043 const token = getTouchingToken(sourceFile, position); 2044 const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined; 2045 const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile); 2046 // We want to order the braces when we return the result. 2047 return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray; 2048 } 2049 2050 function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) { 2051 let start = timestamp(); 2052 const settings = toEditorSettings(editorOptions); 2053 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2054 log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start)); 2055 2056 start = timestamp(); 2057 2058 const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings); 2059 log("getIndentationAtPosition: computeIndentation : " + (timestamp() - start)); 2060 2061 return result; 2062 } 2063 2064 function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { 2065 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2066 return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host)); 2067 } 2068 2069 function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { 2070 return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host)); 2071 } 2072 2073 function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { 2074 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2075 const formatContext = formatting.getFormatContext(toEditorSettings(options), host); 2076 2077 if (!isInComment(sourceFile, position)) { 2078 switch (key) { 2079 case "{": 2080 return formatting.formatOnOpeningCurly(position, sourceFile, formatContext); 2081 case "}": 2082 return formatting.formatOnClosingCurly(position, sourceFile, formatContext); 2083 case ";": 2084 return formatting.formatOnSemicolon(position, sourceFile, formatContext); 2085 case "\n": 2086 return formatting.formatOnEnter(position, sourceFile, formatContext); 2087 } 2088 } 2089 2090 return []; 2091 } 2092 2093 function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] { 2094 synchronizeHostData(); 2095 const sourceFile = getValidSourceFile(fileName); 2096 const span = createTextSpanFromBounds(start, end); 2097 const formatContext = formatting.getFormatContext(formatOptions, host); 2098 2099 return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => { 2100 cancellationToken.throwIfCancellationRequested(); 2101 return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences }); 2102 }); 2103 } 2104 2105 function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions { 2106 synchronizeHostData(); 2107 Debug.assert(scope.type === "file"); 2108 const sourceFile = getValidSourceFile(scope.fileName); 2109 const formatContext = formatting.getFormatContext(formatOptions, host); 2110 2111 return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences }); 2112 } 2113 2114 function organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { 2115 synchronizeHostData(); 2116 Debug.assert(args.type === "file"); 2117 const sourceFile = getValidSourceFile(args.fileName); 2118 const formatContext = formatting.getFormatContext(formatOptions, host); 2119 2120 const mode = args.mode ?? (args.skipDestructiveCodeActions ? OrganizeImportsMode.SortAndCombine : OrganizeImportsMode.All); 2121 return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences, mode); 2122 } 2123 2124 function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { 2125 return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper); 2126 } 2127 2128 function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult>; 2129 function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult[]>; 2130 function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>; 2131 function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>; 2132 function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>; 2133 function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]> { 2134 const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[]; 2135 return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action); 2136 } 2137 2138 function applySingleCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult> { 2139 const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName); 2140 Debug.assertEqual(action.type, "install package"); 2141 return host.installPackage 2142 ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName }) 2143 : Promise.reject("Host does not implement `installPackage`"); 2144 } 2145 2146 function getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { 2147 return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position, options); 2148 } 2149 2150 function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { 2151 // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too 2152 // expensive to do during typing scenarios 2153 // i.e. whether we're dealing with: 2154 // var x = new foo<| ( with class foo<T>{} ) 2155 // or 2156 // var y = 3 <| 2157 if (openingBrace === CharacterCodes.lessThan) { 2158 return false; 2159 } 2160 2161 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2162 2163 // Check if in a context where we don't want to perform any insertion 2164 if (isInString(sourceFile, position)) { 2165 return false; 2166 } 2167 2168 if (isInsideJsxElementOrAttribute(sourceFile, position)) { 2169 return openingBrace === CharacterCodes.openBrace; 2170 } 2171 2172 if (isInTemplateString(sourceFile, position)) { 2173 return false; 2174 } 2175 2176 switch (openingBrace) { 2177 case CharacterCodes.singleQuote: 2178 case CharacterCodes.doubleQuote: 2179 case CharacterCodes.backtick: 2180 return !isInComment(sourceFile, position); 2181 } 2182 2183 return true; 2184 } 2185 2186 function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined { 2187 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2188 const token = findPrecedingToken(position, sourceFile); 2189 if (!token) return undefined; 2190 const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent 2191 : isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined; 2192 if (element && isUnclosedTag(element)) { 2193 return { newText: `</${element.openingElement.tagName.getText(sourceFile)}>` }; 2194 } 2195 const fragment = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningFragment(token.parent) ? token.parent.parent 2196 : isJsxText(token) && isJsxFragment(token.parent) ? token.parent : undefined; 2197 if (fragment && isUnclosedFragment(fragment)) { 2198 return { newText: "</>" }; 2199 } 2200 } 2201 2202 function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) { 2203 return { 2204 lineStarts: sourceFile.getLineStarts(), 2205 firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, 2206 lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line 2207 }; 2208 } 2209 2210 function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { 2211 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2212 const textChanges: TextChange[] = []; 2213 const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); 2214 2215 let isCommenting = insertComment || false; 2216 let leftMostPosition = Number.MAX_VALUE; 2217 const lineTextStarts = new Map<string, number>(); 2218 const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); 2219 const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); 2220 const openComment = isJsx ? "{/*" : "//"; 2221 2222 // Check each line before any text changes. 2223 for (let i = firstLine; i <= lastLine; i++) { 2224 const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); 2225 2226 // Find the start of text and the left-most character. No-op on empty lines. 2227 const regExec = firstNonWhitespaceCharacterRegex.exec(lineText); 2228 if (regExec) { 2229 leftMostPosition = Math.min(leftMostPosition, regExec.index); 2230 lineTextStarts.set(i.toString(), regExec.index); 2231 2232 if (lineText.substr(regExec.index, openComment.length) !== openComment) { 2233 isCommenting = insertComment === undefined || insertComment; 2234 } 2235 } 2236 } 2237 2238 // Push all text changes. 2239 for (let i = firstLine; i <= lastLine; i++) { 2240 // If the range is multiline and ends on a beginning of a line, don't comment/uncomment. 2241 if (firstLine !== lastLine && lineStarts[i] === textRange.end) { 2242 continue; 2243 } 2244 2245 const lineTextStart = lineTextStarts.get(i.toString()); 2246 2247 // If the line is not an empty line; otherwise no-op. 2248 if (lineTextStart !== undefined) { 2249 if (isJsx) { 2250 textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); 2251 } 2252 else if (isCommenting) { 2253 textChanges.push({ 2254 newText: openComment, 2255 span: { 2256 length: 0, 2257 start: lineStarts[i] + leftMostPosition 2258 } 2259 }); 2260 } 2261 else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { 2262 textChanges.push({ 2263 newText: "", 2264 span: { 2265 length: openComment.length, 2266 start: lineStarts[i] + lineTextStart 2267 } 2268 }); 2269 } 2270 } 2271 } 2272 2273 return textChanges; 2274 } 2275 2276 function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { 2277 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2278 const textChanges: TextChange[] = []; 2279 const { text } = sourceFile; 2280 2281 let hasComment = false; 2282 let isCommenting = insertComment || false; 2283 const positions = [] as number[] as SortedArray<number>; 2284 2285 let { pos } = textRange; 2286 const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); 2287 2288 const openMultiline = isJsx ? "{/*" : "/*"; 2289 const closeMultiline = isJsx ? "*/}" : "*/"; 2290 const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; 2291 const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; 2292 2293 // Get all comment positions 2294 while (pos <= textRange.end) { 2295 // Start of comment is considered inside comment. 2296 const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; 2297 const commentRange = isInComment(sourceFile, pos + offset); 2298 2299 // If position is in a comment add it to the positions array. 2300 if (commentRange) { 2301 // Comment range doesn't include the brace character. Increase it to include them. 2302 if (isJsx) { 2303 commentRange.pos--; 2304 commentRange.end++; 2305 } 2306 2307 positions.push(commentRange.pos); 2308 if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { 2309 positions.push(commentRange.end); 2310 } 2311 2312 hasComment = true; 2313 pos = commentRange.end + 1; 2314 } 2315 else { // If it's not in a comment range, then we need to comment the uncommented portions. 2316 const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); 2317 2318 isCommenting = insertComment !== undefined 2319 ? insertComment 2320 : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again. 2321 pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; 2322 } 2323 } 2324 2325 // If it didn't found a comment and isCommenting is false means is only empty space. 2326 // We want to insert comment in this scenario. 2327 if (isCommenting || !hasComment) { 2328 if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { 2329 insertSorted(positions, textRange.pos, compareValues); 2330 } 2331 insertSorted(positions, textRange.end, compareValues); 2332 2333 // Insert open comment if the first position is not a comment already. 2334 const firstPos = positions[0]; 2335 if (text.substr(firstPos, openMultiline.length) !== openMultiline) { 2336 textChanges.push({ 2337 newText: openMultiline, 2338 span: { 2339 length: 0, 2340 start: firstPos 2341 } 2342 }); 2343 } 2344 2345 // Insert open and close comment to all positions between first and last. Exclusive. 2346 for (let i = 1; i < positions.length - 1; i++) { 2347 if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { 2348 textChanges.push({ 2349 newText: closeMultiline, 2350 span: { 2351 length: 0, 2352 start: positions[i] 2353 } 2354 }); 2355 } 2356 2357 if (text.substr(positions[i], openMultiline.length) !== openMultiline) { 2358 textChanges.push({ 2359 newText: openMultiline, 2360 span: { 2361 length: 0, 2362 start: positions[i] 2363 } 2364 }); 2365 } 2366 } 2367 2368 // Insert open comment if the last position is not a comment already. 2369 if (textChanges.length % 2 !== 0) { 2370 textChanges.push({ 2371 newText: closeMultiline, 2372 span: { 2373 length: 0, 2374 start: positions[positions.length - 1] 2375 } 2376 }); 2377 } 2378 } 2379 else { 2380 // If is not commenting then remove all comments found. 2381 for (const pos of positions) { 2382 const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0; 2383 const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; 2384 textChanges.push({ 2385 newText: "", 2386 span: { 2387 length: openMultiline.length, 2388 start: pos - offset 2389 } 2390 }); 2391 } 2392 } 2393 2394 return textChanges; 2395 } 2396 2397 function commentSelection(fileName: string, textRange: TextRange): TextChange[] { 2398 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2399 const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange); 2400 2401 // If there is a selection that is on the same line, add multiline. 2402 return firstLine === lastLine && textRange.pos !== textRange.end 2403 ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true) 2404 : toggleLineComment(fileName, textRange, /*insertComment*/ true); 2405 } 2406 2407 function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { 2408 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2409 const textChanges: TextChange[] = []; 2410 const { pos } = textRange; 2411 let { end } = textRange; 2412 2413 // If cursor is not a selection we need to increase the end position 2414 // to include the start of the comment. 2415 if (pos === end) { 2416 end += isInsideJsxElement(sourceFile, pos) ? 2 : 1; 2417 } 2418 2419 for (let i = pos; i <= end; i++) { 2420 const commentRange = isInComment(sourceFile, i); 2421 if (commentRange) { 2422 switch (commentRange.kind) { 2423 case SyntaxKind.SingleLineCommentTrivia: 2424 textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); 2425 break; 2426 case SyntaxKind.MultiLineCommentTrivia: 2427 textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); 2428 } 2429 2430 i = commentRange.end + 1; 2431 } 2432 } 2433 2434 return textChanges; 2435 } 2436 2437 function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { 2438 return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || 2439 isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); 2440 } 2441 2442 function isUnclosedFragment({ closingFragment, parent }: JsxFragment): boolean { 2443 return !!(closingFragment.flags & NodeFlags.ThisNodeHasError) || (isJsxFragment(parent) && isUnclosedFragment(parent)); 2444 } 2445 2446 function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { 2447 const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); 2448 const range = formatting.getRangeOfEnclosingComment(sourceFile, position); 2449 return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined; 2450 } 2451 2452 function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { 2453 // Note: while getting todo comments seems like a syntactic operation, we actually 2454 // treat it as a semantic operation here. This is because we expect our host to call 2455 // this on every single file. If we treat this syntactically, then that will cause 2456 // us to populate and throw away the tree in our syntax tree cache for each file. By 2457 // treating this as a semantic operation, we can access any tree without throwing 2458 // anything away. 2459 synchronizeHostData(); 2460 2461 const sourceFile = getValidSourceFile(fileName); 2462 2463 cancellationToken.throwIfCancellationRequested(); 2464 2465 const fileContents = sourceFile.text; 2466 const result: TodoComment[] = []; 2467 2468 // Exclude node_modules or oh_modules files as we don't want to show the todos of external libraries. 2469 if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName) && !isOHModulesFile(sourceFile.fileName)) { 2470 const regExp = getTodoCommentsRegExp(); 2471 2472 let matchArray: RegExpExecArray | null; 2473 while (matchArray = regExp.exec(fileContents)) { 2474 cancellationToken.throwIfCancellationRequested(); 2475 2476 // If we got a match, here is what the match array will look like. Say the source text is: 2477 // 2478 // " // hack 1" 2479 // 2480 // The result array with the regexp: will be: 2481 // 2482 // ["// hack 1", "// ", "hack 1", undefined, "hack"] 2483 // 2484 // Here are the relevant capture groups: 2485 // 0) The full match for the entire regexp. 2486 // 1) The preamble to the message portion. 2487 // 2) The message portion. 2488 // 3...N) The descriptor that was matched - by index. 'undefined' for each 2489 // descriptor that didn't match. an actual value if it did match. 2490 // 2491 // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. 2492 // "hack" in position 4 means HACK did match. 2493 const firstDescriptorCaptureIndex = 3; 2494 Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex); 2495 2496 const preamble = matchArray[1]; 2497 const matchPosition = matchArray.index + preamble.length; 2498 2499 // OK, we have found a match in the file. This is only an acceptable match if 2500 // it is contained within a comment. 2501 if (!isInComment(sourceFile, matchPosition)) { 2502 continue; 2503 } 2504 2505 let descriptor: TodoCommentDescriptor | undefined; 2506 for (let i = 0; i < descriptors.length; i++) { 2507 if (matchArray[i + firstDescriptorCaptureIndex]) { 2508 descriptor = descriptors[i]; 2509 } 2510 } 2511 if (descriptor === undefined) return Debug.fail(); 2512 2513 // We don't want to match something like 'TODOBY', so we make sure a non 2514 // letter/digit follows the match. 2515 if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { 2516 continue; 2517 } 2518 2519 const message = matchArray[2]; 2520 result.push({ descriptor, message, position: matchPosition }); 2521 } 2522 } 2523 2524 return result; 2525 2526 function escapeRegExp(str: string): string { 2527 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 2528 } 2529 2530 function getTodoCommentsRegExp(): RegExp { 2531 // NOTE: `?:` means 'non-capture group'. It allows us to have groups without having to 2532 // filter them out later in the final result array. 2533 2534 // TODO comments can appear in one of the following forms: 2535 // 2536 // 1) // TODO or /////////// TODO 2537 // 2538 // 2) /* TODO or /********** TODO 2539 // 2540 // 3) /* 2541 // * TODO 2542 // */ 2543 // 2544 // The following three regexps are used to match the start of the text up to the TODO 2545 // comment portion. 2546 const singleLineCommentStart = /(?:\/\/+\s*)/.source; 2547 const multiLineCommentStart = /(?:\/\*+\s*)/.source; 2548 const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source; 2549 2550 // Match any of the above three TODO comment start regexps. 2551 // Note that the outermost group *is* a capture group. We want to capture the preamble 2552 // so that we can determine the starting position of the TODO comment match. 2553 const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")"; 2554 2555 // Takes the descriptors and forms a regexp that matches them as if they were literals. 2556 // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be: 2557 // 2558 // (?:(TODO\(jason\))|(HACK)) 2559 // 2560 // Note that the outermost group is *not* a capture group, but the innermost groups 2561 // *are* capture groups. By capturing the inner literals we can determine after 2562 // matching which descriptor we are dealing with. 2563 const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")"; 2564 2565 // After matching a descriptor literal, the following regexp matches the rest of the 2566 // text up to the end of the line (or */). 2567 const endOfLineOrEndOfComment = /(?:$|\*\/)/.source; 2568 const messageRemainder = /(?:.*?)/.source; 2569 2570 // This is the portion of the match we'll return as part of the TODO comment result. We 2571 // match the literal portion up to the end of the line or end of comment. 2572 const messagePortion = "(" + literals + messageRemainder + ")"; 2573 const regExpString = preamble + messagePortion + endOfLineOrEndOfComment; 2574 2575 // The final regexp will look like this: 2576 // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim 2577 2578 // The flags of the regexp are important here. 2579 // 'g' is so that we are doing a global search and can find matches several times 2580 // in the input. 2581 // 2582 // 'i' is for case insensitivity (We do this to match C# TODO comment code). 2583 // 2584 // 'm' is so we can find matches in a multi-line input. 2585 return new RegExp(regExpString, "gim"); 2586 } 2587 2588 function isLetterOrDigit(char: number): boolean { 2589 return (char >= CharacterCodes.a && char <= CharacterCodes.z) || 2590 (char >= CharacterCodes.A && char <= CharacterCodes.Z) || 2591 (char >= CharacterCodes._0 && char <= CharacterCodes._9); 2592 } 2593 2594 function isNodeModulesFile(path: string): boolean { 2595 return stringContains(path, "/node_modules/"); 2596 } 2597 2598 function isOHModulesFile(path: string): boolean { 2599 return stringContains(path, "/oh_modules/"); 2600 } 2601 } 2602 2603 function getRenameInfo(fileName: string, position: number, preferences: UserPreferences | RenameInfoOptions | undefined): RenameInfo { 2604 synchronizeHostData(); 2605 return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, preferences || {}); 2606 } 2607 2608 function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext { 2609 const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end]; 2610 return { 2611 file, 2612 startPosition, 2613 endPosition, 2614 program: getProgram()!, 2615 host, 2616 formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217 2617 cancellationToken, 2618 preferences, 2619 triggerReason, 2620 kind 2621 }; 2622 } 2623 2624 function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext { 2625 return { 2626 file, 2627 program: getProgram()!, 2628 host, 2629 span, 2630 preferences, 2631 cancellationToken, 2632 }; 2633 } 2634 2635 function getSmartSelectionRange(fileName: string, position: number): SelectionRange { 2636 return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); 2637 } 2638 2639 function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { 2640 synchronizeHostData(); 2641 const file = getValidSourceFile(fileName); 2642 return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); 2643 } 2644 2645 function getEditsForRefactor( 2646 fileName: string, 2647 formatOptions: FormatCodeSettings, 2648 positionOrRange: number | TextRange, 2649 refactorName: string, 2650 actionName: string, 2651 preferences: UserPreferences = emptyOptions, 2652 ): RefactorEditInfo | undefined { 2653 synchronizeHostData(); 2654 const file = getValidSourceFile(fileName); 2655 return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); 2656 } 2657 2658 function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { 2659 // Go to Definition supports returning a zero-length span at position 0 for 2660 // non-existent files. We need to special-case the conversion of position 0 2661 // to avoid a crash trying to get the text for that file, since this function 2662 // otherwise assumes that 'fileName' is the name of a file that exists. 2663 if (position === 0) { 2664 return { line: 0, character: 0 }; 2665 } 2666 return sourceMapper.toLineColumnOffset(fileName, position); 2667 } 2668 2669 function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { 2670 synchronizeHostData(); 2671 const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); 2672 return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration)); 2673 } 2674 2675 function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { 2676 synchronizeHostData(); 2677 const sourceFile = getValidSourceFile(fileName); 2678 const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); 2679 return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; 2680 } 2681 2682 function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { 2683 synchronizeHostData(); 2684 const sourceFile = getValidSourceFile(fileName); 2685 const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); 2686 return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; 2687 } 2688 2689 function provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences = emptyOptions): InlayHint[] { 2690 synchronizeHostData(); 2691 const sourceFile = getValidSourceFile(fileName); 2692 return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); 2693 } 2694 2695 function updateRootFiles(rootFiles: string[]) { 2696 host.getScriptFileNames = () => rootFiles 2697 } 2698 2699 function getProps(): string[] { 2700 return host.uiProps ? host.uiProps : []; 2701 } 2702 2703 const ls: LanguageService = { 2704 dispose, 2705 cleanupSemanticCache, 2706 getSyntacticDiagnostics, 2707 getSemanticDiagnostics, 2708 getSuggestionDiagnostics, 2709 getCompilerOptionsDiagnostics, 2710 getSyntacticClassifications, 2711 getSemanticClassifications, 2712 getEncodedSyntacticClassifications, 2713 getEncodedSemanticClassifications, 2714 getCompletionsAtPosition, 2715 getCompletionEntryDetails, 2716 getCompletionEntrySymbol, 2717 getSignatureHelpItems, 2718 getQuickInfoAtPosition, 2719 getDefinitionAtPosition, 2720 getDefinitionAndBoundSpan, 2721 getImplementationAtPosition, 2722 getTypeDefinitionAtPosition, 2723 getReferencesAtPosition, 2724 findReferences, 2725 getFileReferences, 2726 getOccurrencesAtPosition, 2727 getDocumentHighlights, 2728 getNameOrDottedNameSpan, 2729 getBreakpointStatementAtPosition, 2730 getNavigateToItems, 2731 getRenameInfo, 2732 getSmartSelectionRange, 2733 findRenameLocations, 2734 getNavigationBarItems, 2735 getNavigationTree, 2736 getOutliningSpans, 2737 getTodoComments, 2738 getBraceMatchingAtPosition, 2739 getIndentationAtPosition, 2740 getFormattingEditsForRange, 2741 getFormattingEditsForDocument, 2742 getFormattingEditsAfterKeystroke, 2743 getDocCommentTemplateAtPosition, 2744 isValidBraceCompletionAtPosition, 2745 getJsxClosingTagAtPosition, 2746 getSpanOfEnclosingComment, 2747 getCodeFixesAtPosition, 2748 getCombinedCodeFix, 2749 applyCodeActionCommand, 2750 organizeImports, 2751 getEditsForFileRename, 2752 getEmitOutput, 2753 getNonBoundSourceFile, 2754 getProgram, 2755 getBuilderProgram, 2756 getCurrentProgram: () => program, 2757 getAutoImportProvider, 2758 updateIsDefinitionOfReferencedSymbols, 2759 getApplicableRefactors, 2760 getEditsForRefactor, 2761 toLineColumnOffset, 2762 getSourceMapper: () => sourceMapper, 2763 clearSourceMapperCache: () => sourceMapper.clearCache(), 2764 prepareCallHierarchy, 2765 provideCallHierarchyIncomingCalls, 2766 provideCallHierarchyOutgoingCalls, 2767 toggleLineComment, 2768 toggleMultilineComment, 2769 commentSelection, 2770 uncommentSelection, 2771 provideInlayHints, 2772 updateRootFiles, 2773 getProps 2774 }; 2775 2776 switch (languageServiceMode) { 2777 case LanguageServiceMode.Semantic: 2778 break; 2779 case LanguageServiceMode.PartialSemantic: 2780 invalidOperationsInPartialSemanticMode.forEach(key => 2781 ls[key] = () => { 2782 throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.PartialSemantic`); 2783 } 2784 ); 2785 break; 2786 case LanguageServiceMode.Syntactic: 2787 invalidOperationsInSyntacticMode.forEach(key => 2788 ls[key] = () => { 2789 throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.Syntactic`); 2790 } 2791 ); 2792 break; 2793 default: 2794 Debug.assertNever(languageServiceMode); 2795 } 2796 return ls; 2797 } 2798 2799 /* @internal */ 2800 /** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */ 2801 export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap<number> { 2802 if (!sourceFile.nameTable) { 2803 initializeNameTable(sourceFile); 2804 } 2805 2806 return sourceFile.nameTable!; // TODO: GH#18217 2807 } 2808 2809 function initializeNameTable(sourceFile: SourceFile): void { 2810 const nameTable = sourceFile.nameTable = new Map(); 2811 sourceFile.forEachChild(function walk(node) { 2812 if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) { 2813 const text = getEscapedTextOfIdentifierOrLiteral(node); 2814 nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); 2815 } 2816 else if (isPrivateIdentifier(node)) { 2817 const text = node.escapedText; 2818 nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); 2819 } 2820 2821 forEachChild(node, walk); 2822 if (hasJSDocNodes(node)) { 2823 for (const jsDoc of node.jsDoc!) { 2824 forEachChild(jsDoc, walk); 2825 } 2826 } 2827 }); 2828 } 2829 2830 /** 2831 * We want to store any numbers/strings if they were a name that could be 2832 * related to a declaration. So, if we have 'import x = require("something")' 2833 * then we want 'something' to be in the name table. Similarly, if we have 2834 * "a['propname']" then we want to store "propname" in the name table. 2835 */ 2836 function literalIsName(node: StringLiteralLike | NumericLiteral): boolean { 2837 return isDeclarationName(node) || 2838 node.parent.kind === SyntaxKind.ExternalModuleReference || 2839 isArgumentOfElementAccessExpression(node) || 2840 isLiteralComputedPropertyDeclarationName(node); 2841 } 2842 2843 /** 2844 * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } 2845 */ 2846 /* @internal */ 2847 export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined { 2848 const element = getContainingObjectLiteralElementWorker(node); 2849 return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined; 2850 } 2851 function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined { 2852 switch (node.kind) { 2853 case SyntaxKind.StringLiteral: 2854 case SyntaxKind.NoSubstitutionTemplateLiteral: 2855 case SyntaxKind.NumericLiteral: 2856 if (node.parent.kind === SyntaxKind.ComputedPropertyName) { 2857 return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; 2858 } 2859 // falls through 2860 2861 case SyntaxKind.Identifier: 2862 return isObjectLiteralElement(node.parent) && 2863 (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) && 2864 node.parent.name === node ? node.parent : undefined; 2865 } 2866 return undefined; 2867 } 2868 2869 /* @internal */ 2870 export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes }; 2871 2872 function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined { 2873 const object = getContainingObjectLiteralElement(node); 2874 if (object) { 2875 const contextualType = checker.getContextualType(object.parent); 2876 const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false); 2877 if (properties && properties.length === 1) { 2878 return first(properties); 2879 } 2880 } 2881 return checker.getSymbolAtLocation(node); 2882 } 2883 2884 /** Gets all symbols for one property. Does not get symbols for every property. */ 2885 /* @internal */ 2886 export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] { 2887 const name = getNameFromPropertyName(node.name); 2888 if (!name) return emptyArray; 2889 if (!contextualType.isUnion()) { 2890 const symbol = contextualType.getProperty(name); 2891 return symbol ? [symbol] : emptyArray; 2892 } 2893 2894 const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); 2895 if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) { 2896 const symbol = contextualType.getProperty(name); 2897 if (symbol) return [symbol]; 2898 } 2899 if (discriminatedPropertySymbols.length === 0) { 2900 // Bad discriminant -- do again without discriminating 2901 return mapDefined(contextualType.types, t => t.getProperty(name)); 2902 } 2903 return discriminatedPropertySymbols; 2904 } 2905 2906 function isArgumentOfElementAccessExpression(node: Node) { 2907 return node && 2908 node.parent && 2909 node.parent.kind === SyntaxKind.ElementAccessExpression && 2910 (node.parent as ElementAccessExpression).argumentExpression === node; 2911 } 2912 2913 /// getDefaultLibraryFilePath 2914 declare const __dirname: string; 2915 2916 /** 2917 * Get the path of the default library files (lib.d.ts) as distributed with the typescript 2918 * node package. 2919 * The functionality is not supported if the ts module is consumed outside of a node module. 2920 */ 2921 export function getDefaultLibFilePath(options: CompilerOptions): string { 2922 // Check __dirname is defined and that we are on a node.js system. 2923 if (typeof __dirname !== "undefined") { 2924 return combinePaths(__dirname, getDefaultLibFileName(options)); 2925 } 2926 2927 throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. "); 2928 } 2929 2930 setObjectAllocator(getServicesObjectAllocator()); 2931} 2932