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 (!isSourceFile(parent) || !isInMarkedKitImport(parent, pos, textPos)) { 188 if (textPos <= end) { 189 if (token === SyntaxKind.Identifier) { 190 Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`); 191 } 192 nodes.push(createNode(token, pos, textPos, parent)); 193 } 194 } 195 pos = textPos; 196 if (token === SyntaxKind.EndOfFileToken) { 197 break; 198 } 199 } 200 } 201 202 function createSyntaxList(nodes: NodeArray<Node>, parent: Node): Node { 203 const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList; 204 list._children = []; 205 let pos = nodes.pos; 206 for (const node of nodes) { 207 // position start === end mean the node is visual 208 if (node.virtual) { 209 continue; 210 } 211 addSyntheticNodes(list._children, pos, node.pos, parent); 212 list._children.push(node); 213 pos = node.end; 214 } 215 addSyntheticNodes(list._children, pos, nodes.end, parent); 216 return list; 217 } 218 219 class TokenOrIdentifierObject implements Node { 220 public kind!: SyntaxKind; 221 public pos: number; 222 public end: number; 223 public flags: NodeFlags; 224 public modifierFlagsCache: ModifierFlags; 225 public transformFlags: TransformFlags; 226 public parent: Node; 227 public symbol!: Symbol; 228 public jsDocComments?: JSDoc[]; 229 230 constructor(pos: number, end: number) { 231 // Set properties in same order as NodeObject 232 this.pos = pos; 233 this.end = end; 234 this.flags = NodeFlags.None; 235 this.modifierFlagsCache = ModifierFlags.None; 236 this.transformFlags = TransformFlags.None; 237 this.parent = undefined!; 238 } 239 240 public getSourceFile(): SourceFile { 241 return getSourceFileOfNode(this); 242 } 243 244 public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { 245 return getTokenPosOfNode(this, sourceFile, includeJsDocComment); 246 } 247 248 public getFullStart(): number { 249 return this.pos; 250 } 251 252 public getEnd(): number { 253 return this.end; 254 } 255 256 public getWidth(sourceFile?: SourceFile): number { 257 return this.getEnd() - this.getStart(sourceFile); 258 } 259 260 public getFullWidth(): number { 261 return this.end - this.pos; 262 } 263 264 public getLeadingTriviaWidth(sourceFile?: SourceFile): number { 265 return this.getStart(sourceFile) - this.pos; 266 } 267 268 public getFullText(sourceFile?: SourceFile): string { 269 return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); 270 } 271 272 public getText(sourceFile?: SourceFile): string { 273 if (!sourceFile) { 274 sourceFile = this.getSourceFile(); 275 } 276 return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); 277 } 278 279 public getChildCount(): number { 280 return this.getChildren().length; 281 } 282 283 public getChildAt(index: number): Node { 284 return this.getChildren()[index]; 285 } 286 287 public getChildren(): Node[] { 288 return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray; 289 } 290 291 public getFirstToken(): Node | undefined { 292 return undefined; 293 } 294 295 public getLastToken(): Node | undefined { 296 return undefined; 297 } 298 299 public forEachChild<T>(): T | undefined { 300 return undefined; 301 } 302 } 303 304 class SymbolObject implements Symbol { 305 flags: SymbolFlags; 306 escapedName: __String; 307 declarations!: Declaration[]; 308 valueDeclaration!: Declaration; 309 310 // Undefined is used to indicate the value has not been computed. If, after computing, the 311 // symbol has no doc comment, then the empty array will be returned. 312 documentationComment?: SymbolDisplayPart[]; 313 tags?: JSDocTagInfo[]; // same 314 315 contextualGetAccessorDocumentationComment?: SymbolDisplayPart[]; 316 contextualSetAccessorDocumentationComment?: SymbolDisplayPart[]; 317 318 contextualGetAccessorTags?: JSDocTagInfo[]; 319 contextualSetAccessorTags?: JSDocTagInfo[]; 320 321 constructor(flags: SymbolFlags, name: __String) { 322 this.flags = flags; 323 this.escapedName = name; 324 } 325 326 getFlags(): SymbolFlags { 327 return this.flags; 328 } 329 330 get name(): string { 331 return symbolName(this); 332 } 333 334 getEscapedName(): __String { 335 return this.escapedName; 336 } 337 338 getName(): string { 339 return this.name; 340 } 341 342 getDeclarations(): Declaration[] | undefined { 343 return this.declarations; 344 } 345 346 getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] { 347 if (!this.documentationComment) { 348 this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs 349 350 if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { 351 const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; 352 this.documentationComment = getDocumentationComment([labelDecl], checker); 353 } 354 else { 355 this.documentationComment = getDocumentationComment(this.declarations, checker); 356 } 357 } 358 return this.documentationComment; 359 } 360 361 getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { 362 if (context) { 363 if (isGetAccessor(context)) { 364 if (!this.contextualGetAccessorDocumentationComment) { 365 this.contextualGetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isGetAccessor), checker); 366 } 367 if (length(this.contextualGetAccessorDocumentationComment)) { 368 return this.contextualGetAccessorDocumentationComment; 369 } 370 } 371 if (isSetAccessor(context)) { 372 if (!this.contextualSetAccessorDocumentationComment) { 373 this.contextualSetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isSetAccessor), checker); 374 } 375 if (length(this.contextualSetAccessorDocumentationComment)) { 376 return this.contextualSetAccessorDocumentationComment; 377 } 378 } 379 } 380 return this.getDocumentationComment(checker); 381 } 382 383 getJsDocTags(checker?: TypeChecker): JSDocTagInfo[] { 384 if (this.tags === undefined) { 385 this.tags = getJsDocTagsOfDeclarations(this.declarations, checker); 386 } 387 388 return this.tags; 389 } 390 391 getContextualJsDocTags(context: Node | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { 392 if (context) { 393 if (isGetAccessor(context)) { 394 if (!this.contextualGetAccessorTags) { 395 this.contextualGetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isGetAccessor), checker); 396 } 397 if (length(this.contextualGetAccessorTags)) { 398 return this.contextualGetAccessorTags; 399 } 400 } 401 if (isSetAccessor(context)) { 402 if (!this.contextualSetAccessorTags) { 403 this.contextualSetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isSetAccessor), checker); 404 } 405 if (length(this.contextualSetAccessorTags)) { 406 return this.contextualSetAccessorTags; 407 } 408 } 409 } 410 return this.getJsDocTags(checker); 411 } 412 } 413 414 class TokenObject<TKind extends SyntaxKind> extends TokenOrIdentifierObject implements Token<TKind> { 415 public kind: TKind; 416 417 constructor(kind: TKind, pos: number, end: number) { 418 super(pos, end); 419 this.kind = kind; 420 } 421 } 422 423 class IdentifierObject extends TokenOrIdentifierObject implements Identifier { 424 public kind: SyntaxKind.Identifier = SyntaxKind.Identifier; 425 public escapedText!: __String; 426 public autoGenerateFlags!: GeneratedIdentifierFlags; 427 _primaryExpressionBrand: any; 428 _memberExpressionBrand: any; 429 _leftHandSideExpressionBrand: any; 430 _updateExpressionBrand: any; 431 _unaryExpressionBrand: any; 432 _expressionBrand: any; 433 _declarationBrand: any; 434 /*@internal*/typeArguments!: NodeArray<TypeNode>; 435 constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) { 436 super(pos, end); 437 } 438 439 get text(): string { 440 return idText(this); 441 } 442 } 443 IdentifierObject.prototype.kind = SyntaxKind.Identifier; 444 class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { 445 public kind!: SyntaxKind.PrivateIdentifier; 446 public escapedText!: __String; 447 public symbol!: Symbol; 448 _primaryExpressionBrand: any; 449 _memberExpressionBrand: any; 450 _leftHandSideExpressionBrand: any; 451 _updateExpressionBrand: any; 452 _unaryExpressionBrand: any; 453 _expressionBrand: any; 454 constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) { 455 super(pos, end); 456 } 457 458 get text(): string { 459 return idText(this); 460 } 461 } 462 PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier; 463 464 class TypeObject implements Type { 465 checker: TypeChecker; 466 flags: TypeFlags; 467 objectFlags?: ObjectFlags; 468 id!: number; 469 symbol!: Symbol; 470 constructor(checker: TypeChecker, flags: TypeFlags) { 471 this.checker = checker; 472 this.flags = flags; 473 } 474 getFlags(): TypeFlags { 475 return this.flags; 476 } 477 getSymbol(): Symbol | undefined { 478 return this.symbol; 479 } 480 getProperties(): Symbol[] { 481 return this.checker.getPropertiesOfType(this); 482 } 483 getProperty(propertyName: string): Symbol | undefined { 484 return this.checker.getPropertyOfType(this, propertyName); 485 } 486 getApparentProperties(): Symbol[] { 487 return this.checker.getAugmentedPropertiesOfType(this); 488 } 489 getCallSignatures(): readonly Signature[] { 490 return this.checker.getSignaturesOfType(this, SignatureKind.Call); 491 } 492 getConstructSignatures(): readonly Signature[] { 493 return this.checker.getSignaturesOfType(this, SignatureKind.Construct); 494 } 495 getStringIndexType(): Type | undefined { 496 return this.checker.getIndexTypeOfType(this, IndexKind.String); 497 } 498 getNumberIndexType(): Type | undefined { 499 return this.checker.getIndexTypeOfType(this, IndexKind.Number); 500 } 501 getBaseTypes(): BaseType[] | undefined { 502 return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined; 503 } 504 isNullableType(): boolean { 505 return this.checker.isNullableType(this); 506 } 507 getNonNullableType(): Type { 508 return this.checker.getNonNullableType(this); 509 } 510 getNonOptionalType(): Type { 511 return this.checker.getNonOptionalType(this); 512 } 513 getConstraint(): Type | undefined { 514 return this.checker.getBaseConstraintOfType(this); 515 } 516 getDefault(): Type | undefined { 517 return this.checker.getDefaultFromTypeParameter(this); 518 } 519 520 isUnion(): this is UnionType { 521 return !!(this.flags & TypeFlags.Union); 522 } 523 isIntersection(): this is IntersectionType { 524 return !!(this.flags & TypeFlags.Intersection); 525 } 526 isUnionOrIntersection(): this is UnionOrIntersectionType { 527 return !!(this.flags & TypeFlags.UnionOrIntersection); 528 } 529 isLiteral(): this is LiteralType { 530 return !!(this.flags & TypeFlags.StringOrNumberLiteral); 531 } 532 isStringLiteral(): this is StringLiteralType { 533 return !!(this.flags & TypeFlags.StringLiteral); 534 } 535 isNumberLiteral(): this is NumberLiteralType { 536 return !!(this.flags & TypeFlags.NumberLiteral); 537 } 538 isTypeParameter(): this is TypeParameter { 539 return !!(this.flags & TypeFlags.TypeParameter); 540 } 541 isClassOrInterface(): this is InterfaceType { 542 return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface); 543 } 544 isClass(): this is InterfaceType { 545 return !!(getObjectFlags(this) & ObjectFlags.Class); 546 } 547 isIndexType(): this is IndexType { 548 return !!(this.flags & TypeFlags.Index); 549 } 550 /** 551 * This polyfills `referenceType.typeArguments` for API consumers 552 */ 553 get typeArguments() { 554 if (getObjectFlags(this) & ObjectFlags.Reference) { 555 return this.checker.getTypeArguments(this as Type as TypeReference); 556 } 557 return undefined; 558 } 559 } 560 561 class SignatureObject implements Signature { 562 flags: SignatureFlags; 563 checker: TypeChecker; 564 declaration!: SignatureDeclaration; 565 typeParameters?: TypeParameter[]; 566 parameters!: Symbol[]; 567 thisParameter!: Symbol; 568 resolvedReturnType!: Type; 569 resolvedTypePredicate: TypePredicate | undefined; 570 minTypeArgumentCount!: number; 571 minArgumentCount!: number; 572 573 // Undefined is used to indicate the value has not been computed. If, after computing, the 574 // symbol has no doc comment, then the empty array will be returned. 575 documentationComment?: SymbolDisplayPart[]; 576 jsDocTags?: JSDocTagInfo[]; // same 577 578 constructor(checker: TypeChecker, flags: SignatureFlags) { 579 this.checker = checker; 580 this.flags = flags; 581 } 582 583 getDeclaration(): SignatureDeclaration { 584 return this.declaration; 585 } 586 getTypeParameters(): TypeParameter[] | undefined { 587 return this.typeParameters; 588 } 589 getParameters(): Symbol[] { 590 return this.parameters; 591 } 592 getReturnType(): Type { 593 return this.checker.getReturnTypeOfSignature(this); 594 } 595 getTypeParameterAtPosition(pos: number): Type { 596 const type = this.checker.getParameterType(this, pos); 597 if (type.isIndexType() && isThisTypeParameter(type.type)) { 598 const constraint = type.type.getConstraint(); 599 if (constraint) { 600 return this.checker.getIndexType(constraint); 601 } 602 } 603 return type; 604 } 605 606 getDocumentationComment(): SymbolDisplayPart[] { 607 return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); 608 } 609 610 getJsDocTags(): JSDocTagInfo[] { 611 return this.jsDocTags || (this.jsDocTags = getJsDocTagsOfDeclarations(singleElementArray(this.declaration), this.checker)); 612 } 613 } 614 615 /** 616 * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it. 617 * @param node the Node in question. 618 * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`. 619 */ 620 function hasJSDocInheritDocTag(node: Node) { 621 return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc" || tag.tagName.text === "inheritdoc"); 622 } 623 624 function getJsDocTagsOfDeclarations(declarations: Declaration[] | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { 625 if (!declarations) return emptyArray; 626 627 let tags = JsDoc.getJsDocTagsFromDeclarations(declarations, checker); 628 if (checker && (tags.length === 0 || declarations.some(hasJSDocInheritDocTag))) { 629 const seenSymbols = new Set<Symbol>(); 630 for (const declaration of declarations) { 631 const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => { 632 if (!seenSymbols.has(symbol)) { 633 seenSymbols.add(symbol); 634 if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { 635 return symbol.getContextualJsDocTags(declaration, checker); 636 } 637 return symbol.declarations?.length === 1 ? symbol.getJsDocTags() : undefined; 638 } 639 }); 640 if (inheritedTags) { 641 tags = [...inheritedTags, ...tags]; 642 } 643 } 644 } 645 return tags; 646 } 647 648 function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { 649 if (!declarations) return emptyArray; 650 651 let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations, checker); 652 if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) { 653 const seenSymbols = new Set<Symbol>(); 654 for (const declaration of declarations) { 655 const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => { 656 if (!seenSymbols.has(symbol)) { 657 seenSymbols.add(symbol); 658 if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { 659 return symbol.getContextualDocumentationComment(declaration, checker); 660 } 661 return symbol.getDocumentationComment(checker); 662 } 663 }); 664 // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs 665 if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc); 666 } 667 } 668 return doc; 669 } 670 671 function findBaseOfDeclaration<T>(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined { 672 const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent; 673 if (!classOrInterfaceDeclaration) return; 674 675 const isStaticMember = hasStaticModifier(declaration); 676 return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => { 677 const baseType = checker.getTypeAtLocation(superTypeNode); 678 const type = isStaticMember && baseType.symbol ? checker.getTypeOfSymbol(baseType.symbol) : baseType; 679 const symbol = checker.getPropertyOfType(type, declaration.symbol.name); 680 return symbol ? cb(symbol) : undefined; 681 }); 682 } 683 684 class SourceFileObject extends NodeObject implements SourceFile { 685 public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile; 686 public _declarationBrand: any; 687 public fileName!: string; 688 public path!: Path; 689 public resolvedPath!: Path; 690 public originalFileName!: string; 691 public text!: string; 692 public scriptSnapshot!: IScriptSnapshot; 693 public lineMap!: readonly number[]; 694 695 public statements!: NodeArray<Statement>; 696 public endOfFileToken!: Token<SyntaxKind.EndOfFileToken>; 697 698 public amdDependencies!: { name: string; path: string }[]; 699 public moduleName!: string; 700 public referencedFiles!: FileReference[]; 701 public typeReferenceDirectives!: FileReference[]; 702 public libReferenceDirectives!: FileReference[]; 703 704 public syntacticDiagnostics!: DiagnosticWithLocation[]; 705 public parseDiagnostics!: DiagnosticWithLocation[]; 706 public bindDiagnostics!: DiagnosticWithLocation[]; 707 public bindSuggestionDiagnostics?: DiagnosticWithLocation[]; 708 709 public isDeclarationFile!: boolean; 710 public isDefaultLib!: boolean; 711 public hasNoDefaultLib!: boolean; 712 public externalModuleIndicator!: Node; // The first node that causes this file to be an external module 713 public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module 714 public nodeCount!: number; 715 public identifierCount!: number; 716 public symbolCount!: number; 717 public version!: string; 718 public scriptKind!: ScriptKind; 719 public languageVersion!: ScriptTarget; 720 public languageVariant!: LanguageVariant; 721 public identifiers!: ESMap<string, string>; 722 public nameTable: UnderscoreEscapedMap<number> | undefined; 723 public resolvedModules: ModeAwareCache<ResolvedModuleFull> | undefined; 724 public resolvedTypeReferenceDirectiveNames!: ModeAwareCache<ResolvedTypeReferenceDirective>; 725 public imports!: readonly StringLiteralLike[]; 726 public moduleAugmentations!: StringLiteral[]; 727 private namedDeclarations: ESMap<string, Declaration[]> | undefined; 728 public ambientModuleNames!: string[]; 729 public checkJsDirective: CheckJsDirective | undefined; 730 public errorExpectations: TextRange[] | undefined; 731 public possiblyContainDynamicImport?: boolean; 732 public pragmas!: PragmaMap; 733 public localJsxFactory: EntityName | undefined; 734 public localJsxNamespace: __String | undefined; 735 736 constructor(kind: SyntaxKind, pos: number, end: number) { 737 super(kind, pos, end); 738 } 739 740 public update(newText: string, textChangeRange: TextChangeRange): SourceFile { 741 return updateSourceFile(this, newText, textChangeRange); 742 } 743 744 public getLineAndCharacterOfPosition(position: number): LineAndCharacter { 745 return getLineAndCharacterOfPosition(this, position); 746 } 747 748 public getLineStarts(): readonly number[] { 749 return getLineStarts(this); 750 } 751 752 public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number { 753 return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits); 754 } 755 756 public getLineEndOfPosition(pos: number): number { 757 const { line } = this.getLineAndCharacterOfPosition(pos); 758 const lineStarts = this.getLineStarts(); 759 760 let lastCharPos: number | undefined; 761 if (line + 1 >= lineStarts.length) { 762 lastCharPos = this.getEnd(); 763 } 764 if (!lastCharPos) { 765 lastCharPos = lineStarts[line + 1] - 1; 766 } 767 768 const fullText = this.getFullText(); 769 // if the new line is "\r\n", we should return the last non-new-line-character position 770 return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos; 771 } 772 773 public getNamedDeclarations(): ESMap<string, Declaration[]> { 774 if (!this.namedDeclarations) { 775 this.namedDeclarations = this.computeNamedDeclarations(); 776 } 777 778 return this.namedDeclarations; 779 } 780 781 private computeNamedDeclarations(): ESMap<string, Declaration[]> { 782 const result = createMultiMap<Declaration>(); 783 784 this.forEachChild(visit); 785 786 return result; 787 788 function addDeclaration(declaration: Declaration) { 789 const name = getDeclarationName(declaration); 790 if (name) { 791 result.add(name, declaration); 792 } 793 } 794 795 function getDeclarations(name: string) { 796 let declarations = result.get(name); 797 if (!declarations) { 798 result.set(name, declarations = []); 799 } 800 return declarations; 801 } 802 803 function getDeclarationName(declaration: Declaration) { 804 const name = getNonAssignedNameOfDeclaration(declaration); 805 return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text 806 : isPropertyName(name) ? getNameFromPropertyName(name) : undefined); 807 } 808 809 function visit(node: Node): void { 810 switch (node.kind) { 811 case SyntaxKind.FunctionDeclaration: 812 case SyntaxKind.FunctionExpression: 813 case SyntaxKind.MethodDeclaration: 814 case SyntaxKind.MethodSignature: 815 const functionDeclaration = node as FunctionLikeDeclaration; 816 const declarationName = getDeclarationName(functionDeclaration); 817 818 if (declarationName) { 819 const declarations = getDeclarations(declarationName); 820 const lastDeclaration = lastOrUndefined(declarations); 821 822 // Check whether this declaration belongs to an "overload group". 823 if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) { 824 // Overwrite the last declaration if it was an overload 825 // and this one is an implementation. 826 if (functionDeclaration.body && !(lastDeclaration as FunctionLikeDeclaration).body) { 827 declarations[declarations.length - 1] = functionDeclaration; 828 } 829 } 830 else { 831 declarations.push(functionDeclaration); 832 } 833 } 834 forEachChild(node, visit); 835 break; 836 837 case SyntaxKind.ClassDeclaration: 838 case SyntaxKind.ClassExpression: 839 case SyntaxKind.StructDeclaration: 840 case SyntaxKind.InterfaceDeclaration: 841 case SyntaxKind.TypeAliasDeclaration: 842 case SyntaxKind.EnumDeclaration: 843 case SyntaxKind.ModuleDeclaration: 844 case SyntaxKind.ImportEqualsDeclaration: 845 case SyntaxKind.ExportSpecifier: 846 case SyntaxKind.ImportSpecifier: 847 case SyntaxKind.ImportClause: 848 case SyntaxKind.NamespaceImport: 849 case SyntaxKind.GetAccessor: 850 case SyntaxKind.SetAccessor: 851 case SyntaxKind.TypeLiteral: 852 addDeclaration(node as Declaration); 853 forEachChild(node, visit); 854 break; 855 856 case SyntaxKind.Parameter: 857 // Only consider parameter properties 858 if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { 859 break; 860 } 861 // falls through 862 863 case SyntaxKind.VariableDeclaration: 864 case SyntaxKind.BindingElement: { 865 const decl = node as VariableDeclaration; 866 if (isBindingPattern(decl.name)) { 867 forEachChild(decl.name, visit); 868 break; 869 } 870 if (decl.initializer) { 871 visit(decl.initializer); 872 } 873 } 874 // falls through 875 case SyntaxKind.EnumMember: 876 case SyntaxKind.PropertyDeclaration: 877 case SyntaxKind.PropertySignature: 878 addDeclaration(node as Declaration); 879 break; 880 881 case SyntaxKind.ExportDeclaration: 882 // Handle named exports case e.g.: 883 // export {a, b as B} from "mod"; 884 const exportDeclaration = node as ExportDeclaration; 885 if (exportDeclaration.exportClause) { 886 if (isNamedExports(exportDeclaration.exportClause)) { 887 forEach(exportDeclaration.exportClause.elements, visit); 888 } 889 else { 890 visit(exportDeclaration.exportClause.name); 891 } 892 } 893 break; 894 895 case SyntaxKind.ImportDeclaration: 896 const importClause = (node as ImportDeclaration).importClause; 897 if (importClause) { 898 // Handle default import case e.g.: 899 // import d from "mod"; 900 if (importClause.name) { 901 addDeclaration(importClause.name); 902 } 903 904 // Handle named bindings in imports e.g.: 905 // import * as NS from "mod"; 906 // import {a, b as B} from "mod"; 907 if (importClause.namedBindings) { 908 if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { 909 addDeclaration(importClause.namedBindings); 910 } 911 else { 912 forEach(importClause.namedBindings.elements, visit); 913 } 914 } 915 } 916 break; 917 918 case SyntaxKind.BinaryExpression: 919 if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) { 920 addDeclaration(node as BinaryExpression); 921 } 922 // falls through 923 924 default: 925 forEachChild(node, visit); 926 } 927 } 928 } 929 } 930 931 class SourceMapSourceObject implements SourceMapSource { 932 lineMap!: number[]; 933 constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { } 934 935 public getLineAndCharacterOfPosition(pos: number): LineAndCharacter { 936 return getLineAndCharacterOfPosition(this, pos); 937 } 938 } 939 940 function getServicesObjectAllocator(): ObjectAllocator { 941 return { 942 getNodeConstructor: () => NodeObject, 943 getTokenConstructor: () => TokenObject, 944 945 getIdentifierConstructor: () => IdentifierObject, 946 getPrivateIdentifierConstructor: () => PrivateIdentifierObject, 947 getSourceFileConstructor: () => SourceFileObject, 948 getSymbolConstructor: () => SymbolObject, 949 getTypeConstructor: () => TypeObject, 950 getSignatureConstructor: () => SignatureObject, 951 getSourceMapSourceConstructor: () => SourceMapSourceObject, 952 }; 953 } 954 955 /// Language Service 956 957 /* @internal */ 958 export interface DisplayPartsSymbolWriter extends EmitTextWriter { 959 displayParts(): SymbolDisplayPart[]; 960 } 961 962 /* @internal */ 963 export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings; 964 export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; 965 export function toEditorSettings(optionsAsMap: MapLike<any>): MapLike<any> { 966 let allPropertiesAreCamelCased = true; 967 for (const key in optionsAsMap) { 968 if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) { 969 allPropertiesAreCamelCased = false; 970 break; 971 } 972 } 973 if (allPropertiesAreCamelCased) { 974 return optionsAsMap; 975 } 976 const settings: MapLike<any> = {}; 977 for (const key in optionsAsMap) { 978 if (hasProperty(optionsAsMap, key)) { 979 const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1); 980 settings[newKey] = optionsAsMap[key]; 981 } 982 } 983 return settings; 984 } 985 986 function isCamelCase(s: string) { 987 return !s.length || s.charAt(0) === s.charAt(0).toLowerCase(); 988 } 989 990 export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) { 991 if (displayParts) { 992 return map(displayParts, displayPart => displayPart.text).join(""); 993 } 994 995 return ""; 996 } 997 998 export function getDefaultCompilerOptions(): CompilerOptions { 999 // Always default to "ScriptTarget.ES5" for the language service 1000 return { 1001 target: ScriptTarget.ES5, 1002 jsx: JsxEmit.Preserve 1003 }; 1004 } 1005 1006 export function getSupportedCodeFixes() { 1007 return codefix.getSupportedErrorCodes(); 1008 } 1009 1010 class SyntaxTreeCache { 1011 // For our syntactic only features, we also keep a cache of the syntax tree for the 1012 // currently edited file. 1013 private currentFileName: string | undefined; 1014 private currentFileVersion: string | undefined; 1015 private currentFileScriptSnapshot: IScriptSnapshot | undefined; 1016 private currentSourceFile: SourceFile | undefined; 1017 1018 constructor(private host: LanguageServiceHost) { 1019 } 1020 1021 public getCurrentSourceFile(fileName: string): SourceFile { 1022 const scriptSnapshot = this.host.getScriptSnapshot(fileName); 1023 if (!scriptSnapshot) { 1024 // The host does not know about this file. 1025 throw new Error("Could not find file: '" + fileName + "'."); 1026 } 1027 1028 const scriptKind = getScriptKind(fileName, this.host); 1029 const version = this.host.getScriptVersion(fileName); 1030 let sourceFile: SourceFile | undefined; 1031 1032 if (this.currentFileName !== fileName) { 1033 // This is a new file, just parse it 1034 const options: CreateSourceFileOptions = { 1035 languageVersion: ScriptTarget.Latest, 1036 impliedNodeFormat: getImpliedNodeFormatForFile( 1037 toPath(fileName, this.host.getCurrentDirectory(), this.host.getCompilerHost?.()?.getCanonicalFileName || hostGetCanonicalFileName(this.host)), 1038 this.host.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), 1039 this.host, 1040 this.host.getCompilationSettings() 1041 ), 1042 setExternalModuleIndicator: getSetExternalModuleIndicator(this.host.getCompilationSettings()) 1043 }; 1044 sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, scriptKind, this.host.getCompilationSettings()); 1045 } 1046 else if (this.currentFileVersion !== version) { 1047 // This is the same file, just a newer version. Incrementally parse the file. 1048 const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!); 1049 sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange, /*aggressiveChecks*/ undefined, this.host.getCompilationSettings()); 1050 } 1051 1052 if (sourceFile) { 1053 // All done, ensure state is up to date 1054 this.currentFileVersion = version; 1055 this.currentFileName = fileName; 1056 this.currentFileScriptSnapshot = scriptSnapshot; 1057 this.currentSourceFile = sourceFile; 1058 } 1059 1060 return this.currentSourceFile!; 1061 } 1062 } 1063 1064 function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) { 1065 sourceFile.version = version; 1066 sourceFile.scriptSnapshot = scriptSnapshot; 1067 } 1068 1069 export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTargetOrOptions: ScriptTarget | CreateSourceFileOptions, version: string, setNodeParents: boolean, scriptKind?: ScriptKind, option?: CompilerOptions): SourceFile { 1070 const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTargetOrOptions, setNodeParents, scriptKind, option); 1071 setSourceFileFields(sourceFile, scriptSnapshot, version); 1072 return sourceFile; 1073 } 1074 1075 export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean, option?: CompilerOptions): SourceFile { 1076 // If we were given a text change range, and our version or open-ness changed, then 1077 // incrementally parse this file. 1078 if (textChangeRange) { 1079 if (version !== sourceFile.version) { 1080 let newText: string; 1081 1082 // grab the fragment from the beginning of the original text to the beginning of the span 1083 const prefix = textChangeRange.span.start !== 0 1084 ? sourceFile.text.substr(0, textChangeRange.span.start) 1085 : ""; 1086 1087 // grab the fragment from the end of the span till the end of the original text 1088 const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length 1089 ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) 1090 : ""; 1091 1092 if (textChangeRange.newLength === 0) { 1093 // edit was a deletion - just combine prefix and suffix 1094 newText = prefix && suffix ? prefix + suffix : prefix || suffix; 1095 } 1096 else { 1097 // it was actual edit, fetch the fragment of new text that correspond to new span 1098 const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); 1099 // combine prefix, changed text and suffix 1100 newText = prefix && suffix 1101 ? prefix + changedText + suffix 1102 : prefix 1103 ? (prefix + changedText) 1104 : (changedText + suffix); 1105 } 1106 1107 const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks, option); 1108 setSourceFileFields(newSourceFile, scriptSnapshot, version); 1109 // after incremental parsing nameTable might not be up-to-date 1110 // drop it so it can be lazily recreated later 1111 newSourceFile.nameTable = undefined; 1112 1113 // dispose all resources held by old script snapshot 1114 if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { 1115 if (sourceFile.scriptSnapshot.dispose) { 1116 sourceFile.scriptSnapshot.dispose(); 1117 } 1118 1119 sourceFile.scriptSnapshot = undefined; 1120 } 1121 1122 return newSourceFile; 1123 } 1124 } 1125 1126 const options: CreateSourceFileOptions = { 1127 languageVersion: sourceFile.languageVersion, 1128 impliedNodeFormat: sourceFile.impliedNodeFormat, 1129 setExternalModuleIndicator: sourceFile.setExternalModuleIndicator, 1130 }; 1131 // Otherwise, just create a new source file. 1132 return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, sourceFile.scriptKind, option); 1133 } 1134 1135 const NoopCancellationToken: CancellationToken = { 1136 isCancellationRequested: returnFalse, 1137 throwIfCancellationRequested: noop, 1138 }; 1139 1140 class CancellationTokenObject implements CancellationToken { 1141 constructor(private cancellationToken: HostCancellationToken) { 1142 } 1143 1144 public isCancellationRequested(): boolean { 1145 return this.cancellationToken.isCancellationRequested(); 1146 } 1147 1148 public throwIfCancellationRequested(): void { 1149 if (this.isCancellationRequested()) { 1150 tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "CancellationTokenObject" }); 1151 throw new OperationCanceledException(); 1152 } 1153 } 1154 } 1155 1156 /* @internal */ 1157 /** A cancellation that throttles calls to the host */ 1158 export class ThrottledCancellationToken implements CancellationToken { 1159 // Store when we last tried to cancel. Checking cancellation can be expensive (as we have 1160 // to marshall over to the host layer). So we only bother actually checking once enough 1161 // time has passed. 1162 private lastCancellationCheckTime = 0; 1163 1164 constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) { 1165 } 1166 1167 public isCancellationRequested(): boolean { 1168 const time = timestamp(); 1169 const duration = Math.abs(time - this.lastCancellationCheckTime); 1170 if (duration >= this.throttleWaitMilliseconds) { 1171 // Check no more than once every throttle wait milliseconds 1172 this.lastCancellationCheckTime = time; 1173 return this.hostCancellationToken.isCancellationRequested(); 1174 } 1175 1176 return false; 1177 } 1178 1179 public throwIfCancellationRequested(): void { 1180 if (this.isCancellationRequested()) { 1181 tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "ThrottledCancellationToken" }); 1182 throw new OperationCanceledException(); 1183 } 1184 } 1185 } 1186 1187 const invalidOperationsInPartialSemanticMode: readonly (keyof LanguageService)[] = [ 1188 "getSemanticDiagnostics", 1189 "getSuggestionDiagnostics", 1190 "getCompilerOptionsDiagnostics", 1191 "getSemanticClassifications", 1192 "getEncodedSemanticClassifications", 1193 "getCodeFixesAtPosition", 1194 "getCombinedCodeFix", 1195 "applyCodeActionCommand", 1196 "organizeImports", 1197 "getEditsForFileRename", 1198 "getEmitOutput", 1199 "getApplicableRefactors", 1200 "getEditsForRefactor", 1201 "prepareCallHierarchy", 1202 "provideCallHierarchyIncomingCalls", 1203 "provideCallHierarchyOutgoingCalls", 1204 "provideInlayHints" 1205 ]; 1206 1207 const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [ 1208 ...invalidOperationsInPartialSemanticMode, 1209 "getCompletionsAtPosition", 1210 "getCompletionEntryDetails", 1211 "getCompletionEntrySymbol", 1212 "getSignatureHelpItems", 1213 "getQuickInfoAtPosition", 1214 "getDefinitionAtPosition", 1215 "getDefinitionAndBoundSpan", 1216 "getImplementationAtPosition", 1217 "getTypeDefinitionAtPosition", 1218 "getReferencesAtPosition", 1219 "findReferences", 1220 "getOccurrencesAtPosition", 1221 "getDocumentHighlights", 1222 "getNavigateToItems", 1223 "getRenameInfo", 1224 "findRenameLocations", 1225 "getApplicableRefactors", 1226 ]; 1227 export function createLanguageService( 1228 host: LanguageServiceHost, 1229 documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), 1230 syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode, 1231 ): LanguageService { 1232 let languageServiceMode: LanguageServiceMode; 1233 if (syntaxOnlyOrLanguageServiceMode === undefined) { 1234 languageServiceMode = LanguageServiceMode.Semantic; 1235 } 1236 else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") { 1237 // languageServiceMode = SyntaxOnly 1238 languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.Syntactic : LanguageServiceMode.Semantic; 1239 } 1240 else { 1241 languageServiceMode = syntaxOnlyOrLanguageServiceMode; 1242 } 1243 1244 const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); 1245 let program: Program; 1246 let builderProgram: BuilderProgram; 1247 let lastProjectVersion: string; 1248 let lastTypesRootVersion = 0; 1249 1250 const cancellationToken = host.getCancellationToken 1251 ? new CancellationTokenObject(host.getCancellationToken()) 1252 : NoopCancellationToken; 1253 1254 const currentDirectory = host.getCurrentDirectory(); 1255 1256 // Checks if the localized messages json is set, and if not, query the host for it 1257 maybeSetLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages?.bind(host)); 1258 1259 function log(message: string) { 1260 if (host.log) { 1261 host.log(message); 1262 } 1263 } 1264 1265 const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); 1266 const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); 1267 1268 const sourceMapper = getSourceMapper({ 1269 useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, 1270 getCurrentDirectory: () => currentDirectory, 1271 getProgram, 1272 fileExists: maybeBind(host, host.fileExists), 1273 readFile: maybeBind(host, host.readFile), 1274 getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), 1275 getSourceFileLike: maybeBind(host, host.getSourceFileLike), 1276 log 1277 }); 1278 1279 function getValidSourceFile(fileName: string): SourceFile { 1280 const sourceFile = program.getSourceFile(fileName); 1281 if (!sourceFile) { 1282 const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`); 1283 1284 // We've been having trouble debugging this, so attach sidecar data for the tsserver log. 1285 // See https://github.com/microsoft/TypeScript/issues/30180. 1286 error.ProgramFiles = program.getSourceFiles().map(f => f.fileName); 1287 1288 throw error; 1289 } 1290 return sourceFile; 1291 } 1292 1293 function synchronizeHostData(isCreateIncrementalProgramMode: boolean = false, withLinterProgram?: boolean): void { 1294 Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic); 1295 // perform fast check if host supports it 1296 if (host.getProjectVersion) { 1297 const hostProjectVersion = host.getProjectVersion(); 1298 if (hostProjectVersion) { 1299 if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames?.()) { 1300 return; 1301 } 1302 1303 lastProjectVersion = hostProjectVersion; 1304 } 1305 } 1306 1307 const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; 1308 if (lastTypesRootVersion !== typeRootsVersion) { 1309 log("TypeRoots version has changed; provide new program"); 1310 program = undefined!; // TODO: GH#18217 1311 lastTypesRootVersion = typeRootsVersion; 1312 } 1313 1314 // This array is retained by the program and will be used to determine if the program is up to date, 1315 // so we need to make a copy in case the host mutates the underlying array - otherwise it would look 1316 // like every program always has the host's current list of root files. 1317 const rootFileNames = host.getScriptFileNames().slice(); 1318 1319 // Get a fresh cache of the host information 1320 const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); 1321 const hasInvalidatedResolutions: HasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse; 1322 const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames); 1323 const projectReferences = host.getProjectReferences?.(); 1324 let parsedCommandLines: ESMap<Path, ParsedCommandLine | false> | undefined; 1325 1326 // Now create a new compiler 1327 let compilerHost: CompilerHost | undefined = { 1328 getSourceFile: getOrCreateSourceFile, 1329 getSourceFileByPath: getOrCreateSourceFileByPath, 1330 getCancellationToken: () => cancellationToken, 1331 getCanonicalFileName, 1332 useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, 1333 getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), 1334 getDefaultLibFileName: options => host.getDefaultLibFileName(options), 1335 writeFile: noop, 1336 getCurrentDirectory: () => currentDirectory, 1337 fileExists: fileName => host.fileExists(fileName), 1338 readFile: fileName => host.readFile && host.readFile(fileName), 1339 getSymlinkCache: maybeBind(host, host.getSymlinkCache), 1340 realpath: maybeBind(host, host.realpath), 1341 directoryExists: directoryName => { 1342 return directoryProbablyExists(directoryName, host); 1343 }, 1344 getDirectories: path => { 1345 return host.getDirectories ? host.getDirectories(path) : []; 1346 }, 1347 readDirectory: (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) => { 1348 Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'"); 1349 return host.readDirectory!(path, extensions, exclude, include, depth); 1350 }, 1351 onReleaseOldSourceFile, 1352 onReleaseParsedCommandLine, 1353 hasInvalidatedResolutions, 1354 hasChangedAutomaticTypeDirectiveNames, 1355 trace: maybeBind(host, host.trace), 1356 resolveModuleNames: maybeBind(host, host.resolveModuleNames), 1357 getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache), 1358 resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives), 1359 useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect), 1360 getParsedCommandLine, 1361 getJsDocNodeCheckedConfig: maybeBind(host, host.getJsDocNodeCheckedConfig), 1362 getJsDocNodeConditionCheckedResult: maybeBind(host, host.getJsDocNodeConditionCheckedResult), 1363 getFileCheckedModuleInfo: maybeBind(host, host.getFileCheckedModuleInfo), 1364 }; 1365 1366 const originalGetSourceFile = compilerHost.getSourceFile; 1367 1368 const { getSourceFileWithCache } = changeCompilerHostLikeToUseCache( 1369 compilerHost, 1370 fileName => toPath(fileName, currentDirectory, getCanonicalFileName), 1371 (...args) => originalGetSourceFile.call(compilerHost, ...args) 1372 ); 1373 compilerHost.getSourceFile = getSourceFileWithCache!; 1374 1375 host.setCompilerHost?.(compilerHost); 1376 1377 const parseConfigHost: ParseConfigFileHost = { 1378 useCaseSensitiveFileNames, 1379 fileExists: fileName => compilerHost!.fileExists(fileName), 1380 readFile: fileName => compilerHost!.readFile(fileName), 1381 readDirectory: (...args) => compilerHost!.readDirectory!(...args), 1382 trace: compilerHost.trace, 1383 getCurrentDirectory: compilerHost.getCurrentDirectory, 1384 onUnRecoverableConfigFileDiagnostic: noop, 1385 }; 1386 1387 // The call to isProgramUptoDate below may refer back to documentRegistryBucketKey; 1388 // calculate this early so it's not undefined if downleveled to a var (or, if emitted 1389 // as a const variable without downleveling, doesn't crash). 1390 const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); 1391 1392 // If the program is already up-to-date, we can reuse it 1393 if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { 1394 return; 1395 } 1396 1397 // clear ArkUI collected properties 1398 host.clearProps && host.clearProps(); 1399 1400 compilerHost.getLastCompiledProgram = () => { return program; } 1401 1402 // IMPORTANT - It is critical from this moment onward that we do not check 1403 // cancellation tokens. We are about to mutate source files from a previous program 1404 // instance. If we cancel midway through, we may end up in an inconsistent state where 1405 // the program points to old source files that have been invalidated because of 1406 // incremental parsing. 1407 1408 const options: CreateProgramOptions = { 1409 rootNames: rootFileNames, 1410 options: newSettings, 1411 host: compilerHost, 1412 oldProgram: program, 1413 projectReferences 1414 }; 1415 1416 if (isCreateIncrementalProgramMode) { 1417 if (withLinterProgram) { 1418 builderProgram = createIncrementalProgramForArkTs(options); 1419 } else { 1420 builderProgram = createIncrementalProgram(options); 1421 } 1422 program = builderProgram.getProgram(); 1423 } else { 1424 program = createProgram(options); 1425 } 1426 1427 // 'getOrCreateSourceFile' depends on caching but should be used past this point. 1428 // After this point, the cache needs to be cleared to allow all collected snapshots to be released 1429 compilerHost = undefined; 1430 parsedCommandLines = undefined; 1431 1432 // 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, 1433 // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during 1434 // the course of whatever called `synchronizeHostData` 1435 sourceMapper.clearCache(); 1436 1437 // Make sure all the nodes in the program are both bound, and have their parent 1438 // pointers set property. 1439 program.getTypeChecker(); 1440 return; 1441 1442 function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined { 1443 const path = toPath(fileName, currentDirectory, getCanonicalFileName); 1444 const existing = parsedCommandLines?.get(path); 1445 if (existing !== undefined) return existing || undefined; 1446 1447 const result = host.getParsedCommandLine ? 1448 host.getParsedCommandLine(fileName) : 1449 getParsedCommandLineOfConfigFileUsingSourceFile(fileName); 1450 (parsedCommandLines ||= new Map()).set(path, result || false); 1451 return result; 1452 } 1453 1454 function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined { 1455 const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined; 1456 if (!result) return undefined; 1457 result.path = toPath(configFileName, currentDirectory, getCanonicalFileName); 1458 result.resolvedPath = result.path; 1459 result.originalFileName = result.fileName; 1460 return parseJsonSourceFileConfigFileContent( 1461 result, 1462 parseConfigHost, 1463 getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), 1464 /*optionsToExtend*/ undefined, 1465 getNormalizedAbsolutePath(configFileName, currentDirectory), 1466 ); 1467 } 1468 1469 function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) { 1470 if (host.getParsedCommandLine) { 1471 host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions); 1472 } 1473 else if (oldResolvedRef) { 1474 onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions); 1475 } 1476 } 1477 1478 // Release any files we have acquired in the old program but are 1479 // not part of the new program. 1480 function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { 1481 const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); 1482 documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); 1483 } 1484 1485 function getOrCreateSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { 1486 return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersionOrOptions, onError, shouldCreateNewSourceFile); 1487 } 1488 1489 function getOrCreateSourceFileByPath(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { 1490 Debug.assert(compilerHost, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host."); 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