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