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