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