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