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