diff --git a/pkg/checker/checker.go b/pkg/checker/checker.go index 0f4404cdd..f6807ad87 100644 --- a/pkg/checker/checker.go +++ b/pkg/checker/checker.go @@ -2030,43 +2030,25 @@ func isPropertyImmediatelyReferencedWithinDeclaration(declaration *ast.Node, usa return true } +// Return the type-only declaration node (if any) for the given alias symbol (non-transitively) func (c *Checker) getTypeOnlyAliasDeclaration(symbol *ast.Symbol) *ast.Node { - return c.getTypeOnlyAliasDeclarationEx(symbol, ast.SymbolFlagsNone) + if symbol.Flags&ast.SymbolFlagsAlias != 0 { + c.resolveAlias(symbol) + return c.aliasSymbolLinks.Get(symbol).typeOnlyDeclaration + } + return nil } -func (c *Checker) getTypeOnlyAliasDeclarationEx(symbol *ast.Symbol, include ast.SymbolFlags) *ast.Node { - if symbol.Flags&ast.SymbolFlagsAlias == 0 { - return nil - } - links := c.aliasSymbolLinks.Get(symbol) - if !links.typeOnlyDeclarationResolved { - // We need to set a WIP value here to prevent reentrancy during `getImmediateAliasedSymbol` which, paradoxically, can depend on this - links.typeOnlyDeclarationResolved = true - resolved := c.resolveSymbol(symbol) - // While usually the alias will have been marked during the pass by the full typecheck, we may still need to calculate the alias declaration now - var immediateTarget *ast.Symbol - if c.getDeclarationOfAliasSymbol(symbol) != nil { - immediateTarget = c.getImmediateAliasedSymbol(symbol) - } - c.markSymbolOfAliasDeclarationIfTypeOnly(core.FirstOrNil(symbol.Declarations), immediateTarget, resolved, true /*overwriteEmpty*/, nil, "") - } - if include == ast.SymbolFlagsNone { - return links.typeOnlyDeclaration - } - if links.typeOnlyDeclaration != nil { - var resolved *ast.Symbol - if links.typeOnlyDeclaration.Kind == ast.KindExportDeclaration { - name := links.typeOnlyExportStarName - if name == "" { - name = symbol.Name - } - resolved = c.resolveSymbol(c.getExportsOfModule(links.typeOnlyDeclaration.Symbol().Parent)[name]) - } else { - resolved = c.resolveAlias(links.typeOnlyDeclaration.Symbol()) - } - if c.getSymbolFlags(resolved)&include != 0 { +// Return the first type-only alias declaration node (if any) in the resolution chain that affects +// the symbol for the given meaning +func (c *Checker) getTypeOnlyAliasDeclarationEx(symbol *ast.Symbol, meaning ast.SymbolFlags) *ast.Node { + for symbol.Flags&ast.SymbolFlagsAlias != 0 && symbol.Flags&meaning == 0 { + resolved := c.resolveAlias(symbol) + links := c.aliasSymbolLinks.Get(symbol) + if links.typeOnlyDeclaration != nil { return links.typeOnlyDeclaration } + symbol = resolved } return nil } @@ -2079,9 +2061,8 @@ func (c *Checker) getImmediateAliasedSymbol(symbol *ast.Symbol) *ast.Symbol { if node == nil { panic("Unexpected nil in getImmediateAliasedSymbol") } - links.immediateTarget = c.getTargetOfAliasDeclaration(node, true /*dontRecursivelyResolve*/) + links.immediateTarget = c.getTargetOfAliasDeclaration(node) } - return links.immediateTarget } @@ -8157,7 +8138,7 @@ func (c *Checker) checkImportCallExpression(node *ast.Node) *Type { // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal moduleSymbol := c.resolveExternalModuleName(node, specifier, false /*ignoreErrors*/) if moduleSymbol != nil { - esModuleSymbol := c.resolveESModuleSymbol(moduleSymbol, specifier, true /*dontResolveAlias*/, false /*suppressInteropError*/) + esModuleSymbol := c.resolveExternalModuleSymbol(moduleSymbol, true /*dontResolveAlias*/) if esModuleSymbol != nil { syntheticType := c.getTypeWithSyntheticDefaultOnly(c.getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) if syntheticType == nil { @@ -13552,11 +13533,6 @@ func (c *Checker) getDiagnostics(ctx context.Context, sourceFile *ast.SourceFile return collection.GetDiagnostics() } -func (c *Checker) GetDiagnosticsWithoutCheck(sourceFile *ast.SourceFile) []*ast.Diagnostic { - c.checkNotCanceled() - return c.diagnostics.GetDiagnosticsForFile(sourceFile.FileName()) -} - func (c *Checker) GetGlobalDiagnostics() []*ast.Diagnostic { c.checkNotCanceled() return c.diagnostics.GetGlobalDiagnostics() @@ -14025,7 +14001,7 @@ func (c *Checker) resolveSymbolEx(symbol *ast.Symbol, dontResolveAlias bool) *as return symbol } -func (c *Checker) getTargetOfImportEqualsDeclaration(node *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getTargetOfImportEqualsDeclaration(node *ast.Node) *ast.Symbol { // Node is ImportEqualsDeclaration | VariableDeclaration if ast.IsVariableDeclaration(node) || node.AsImportEqualsDeclaration().ModuleReference.Kind == ast.KindExternalModuleReference { moduleReference := getExternalModuleRequireArgument(node) @@ -14033,17 +14009,17 @@ func (c *Checker) getTargetOfImportEqualsDeclaration(node *ast.Node, dontResolve moduleReference = ast.GetExternalModuleImportEqualsDeclarationExpression(node) } immediate := c.resolveExternalModuleName(node, moduleReference, false /*ignoreErrors*/) - resolved := c.resolveExternalModuleSymbol(immediate, false /*dontResolveAlias*/) + resolved := c.resolveExternalModuleSymbol(immediate, true /*dontResolveAlias*/) if resolved != nil && core.ModuleKindNode20 <= c.moduleKind && c.moduleKind <= core.ModuleKindNodeNext { - moduleExports := c.getExportOfModule(resolved, ast.InternalSymbolNameModuleExports, node, dontResolveAlias) + moduleExports := c.getExportOfModule(resolved, ast.InternalSymbolNameModuleExports, node, true /*dontResolveAlias*/) if moduleExports != nil { return moduleExports } } - c.markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, false /*overwriteEmpty*/, nil, "") + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } - resolved := c.getSymbolOfPartOfRightHandSideOfImportEquals(node.AsImportEqualsDeclaration().ModuleReference, dontResolveAlias) + resolved := c.getSymbolOfPartOfRightHandSideOfImportEquals(node.AsImportEqualsDeclaration().ModuleReference) c.checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved) return resolved } @@ -14060,7 +14036,7 @@ func (c *Checker) resolveExternalModuleTypeByLiteral(name *ast.Node) *Type { } // This function is only for imports with entity names -func (c *Checker) getSymbolOfPartOfRightHandSideOfImportEquals(entityName *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getSymbolOfPartOfRightHandSideOfImportEquals(entityName *ast.Node) *ast.Symbol { // There are three things we might try to look for. In the following examples, // the search term is enclosed in |...|: // @@ -14072,38 +14048,52 @@ func (c *Checker) getSymbolOfPartOfRightHandSideOfImportEquals(entityName *ast.N } // Check for case 1 and 3 in the above example if entityName.Kind == ast.KindIdentifier || entityName.Parent.Kind == ast.KindQualifiedName { - return c.resolveEntityName(entityName, ast.SymbolFlagsNamespace, false /*ignoreErrors*/, dontResolveAlias, nil /*location*/) + return c.resolveEntityName(entityName, ast.SymbolFlagsNamespace, false /*ignoreErrors*/, true /*dontResolveAlias*/, nil /*location*/) } // Case 2 in above example // entityName.kind could be a QualifiedName or a Missing identifier debug.Assert(entityName.Parent.Kind == ast.KindImportEqualsDeclaration) - return c.resolveEntityName(entityName, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, false /*ignoreErrors*/, dontResolveAlias, nil /*location*/) + return c.resolveEntityName(entityName, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, false /*ignoreErrors*/, true /*dontResolveAlias*/, nil /*location*/) } func (c *Checker) checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node *ast.Node, resolved *ast.Symbol) { decl := node.AsImportEqualsDeclaration() - if c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil /*immediateTarget*/, resolved, false /*overwriteEmpty*/, nil, "") && !decl.IsTypeOnly { - typeOnlyDeclaration := c.getTypeOnlyAliasDeclaration(c.getSymbolOfDeclaration(node)) - isExport := ast.NodeKindIs(typeOnlyDeclaration, ast.KindExportSpecifier, ast.KindExportDeclaration) - message := core.IfElse(isExport, - diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type, - diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type) - relatedMessage := core.IfElse(isExport, - diagnostics.X_0_was_exported_here, - diagnostics.X_0_was_imported_here) - // TODO: how to get name for export *? - name := "*" - if !ast.IsExportDeclaration(typeOnlyDeclaration) { - name = typeOnlyDeclaration.Name().Text() + name := decl.ModuleReference + for { + if typeOnlyDeclaration := c.getTypeOnlyDeclarationOfEntityName(name); typeOnlyDeclaration != nil { + isExport := ast.NodeKindIs(typeOnlyDeclaration, ast.KindExportSpecifier, ast.KindExportDeclaration) + message := core.IfElse(isExport, + diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type, + diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type) + relatedMessage := core.IfElse(isExport, + diagnostics.X_0_was_exported_here, + diagnostics.X_0_was_imported_here) + // TODO: how to get name for export *? + name := "*" + if !ast.IsExportDeclaration(typeOnlyDeclaration) { + name = typeOnlyDeclaration.Name().Text() + } + c.error(decl.ModuleReference, message).AddRelatedInfo(createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)) + break } - c.error(decl.ModuleReference, message).AddRelatedInfo(createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)) + if ast.IsIdentifier(name) { + break + } + name = name.AsQualifiedName().Left } } -func (c *Checker) getTargetOfImportClause(node *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getTypeOnlyDeclarationOfEntityName(name *ast.Node) *ast.Node { + if symbol := c.resolveEntityName(name, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, true /*ignoreErrors*/, true /*dontResolveAlias*/, nil /*location*/); symbol != nil { + return c.getTypeOnlyAliasDeclaration(symbol) + } + return nil +} + +func (c *Checker) getTargetOfImportClause(node *ast.Node) *ast.Symbol { moduleSymbol := c.resolveExternalModuleName(node, getModuleSpecifierFromNode(node.Parent), false /*ignoreErrors*/) if moduleSymbol != nil { - return c.getTargetOfModuleDefault(moduleSymbol, node, dontResolveAlias) + return c.getTargetOfModuleDefault(moduleSymbol, node, true /*dontResolveAlias*/) } return nil } @@ -14140,7 +14130,7 @@ func (c *Checker) getTargetOfModuleDefault(moduleSymbol *ast.Symbol, node *ast.N c.error(node.Name(), diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, c.symbolToString(moduleSymbol), "esModuleInterop") return nil } - c.markSymbolOfAliasDeclarationIfTypeOnly(node, exportModuleDotExportsSymbol /*finalTarget*/, nil /*overwriteEmpty*/, false, nil, "") + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return exportModuleDotExportsSymbol } else { exportDefaultSymbol = c.resolveExportByName(moduleSymbol, ast.InternalSymbolNameDefault, node, dontResolveAlias) @@ -14176,10 +14166,10 @@ func (c *Checker) getTargetOfModuleDefault(moduleSymbol *ast.Symbol, node *ast.N if resolved == nil { resolved = c.resolveSymbolEx(moduleSymbol, dontResolveAlias) } - c.markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved /*overwriteEmpty*/, false, nil, "") + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } - c.markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol /*finalTarget*/, nil /*overwriteEmpty*/, false, nil, "") + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return exportDefaultSymbol } @@ -14216,37 +14206,37 @@ func (c *Checker) resolveExportByName(moduleSymbol *ast.Symbol, name string, sou exportSymbol = moduleSymbol.Exports[name] } resolved := c.resolveSymbolEx(exportSymbol, dontResolveAlias) - c.markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, false /*overwriteEmpty*/, nil, "") + c.markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, nil) return resolved } -func (c *Checker) getTargetOfNamespaceImport(node *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getTargetOfNamespaceImport(node *ast.Node) *ast.Symbol { moduleSpecifier := c.getModuleSpecifierForImportOrExport(node) immediate := c.resolveExternalModuleName(node, moduleSpecifier, false /*ignoreErrors*/) - resolved := c.resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias /*suppressInteropError*/, false) - c.markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, false /*overwriteEmpty*/, nil, "") + resolved := c.resolveESModuleSymbol(immediate, node, moduleSpecifier, false /*suppressInteropError*/) + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } -func (c *Checker) getTargetOfNamespaceExport(node *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getTargetOfNamespaceExport(node *ast.Node) *ast.Symbol { moduleSpecifier := c.getModuleSpecifierForImportOrExport(node) if moduleSpecifier != nil { immediate := c.resolveExternalModuleName(node, moduleSpecifier, false /*ignoreErrors*/) - resolved := c.resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias /*suppressInteropError*/, false) - c.markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, false /*overwriteEmpty*/, nil, "") + resolved := c.resolveESModuleSymbol(immediate, node, moduleSpecifier, false /*suppressInteropError*/) + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } return nil } -func (c *Checker) getTargetOfImportSpecifier(node *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getTargetOfImportSpecifier(node *ast.Node) *ast.Symbol { name := node.PropertyNameOrName() if ast.IsImportSpecifier(node) && ast.ModuleExportNameIsDefault(name) { specifier := c.getModuleSpecifierForImportOrExport(node) if specifier != nil { moduleSymbol := c.resolveExternalModuleName(node, specifier, false /*ignoreErrors*/) if moduleSymbol != nil { - return c.getTargetOfModuleDefault(moduleSymbol, node, dontResolveAlias) + return c.getTargetOfModuleDefault(moduleSymbol, node, true /*dontResolveAlias*/) } } } @@ -14254,8 +14244,8 @@ func (c *Checker) getTargetOfImportSpecifier(node *ast.Node, dontResolveAlias bo if ast.IsBindingElement(node) { root = ast.GetRootDeclaration(node) } - resolved := c.getExternalModuleMember(root, node, dontResolveAlias) - c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil /*immediateTarget*/, resolved, false /*overwriteEmpty*/, nil, "") + resolved := c.getExternalModuleMember(root, node, true /*dontResolveAlias*/) + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } @@ -14278,7 +14268,7 @@ func (c *Checker) getExternalModuleMember(node *ast.Node, specifier *ast.Node, d } nameText := name.Text() suppressInteropError := node.Kind == ast.KindVariableDeclaration || nameText == ast.InternalSymbolNameDefault && c.allowSyntheticDefaultImports - targetSymbol := c.resolveESModuleSymbol(moduleSymbol, moduleSpecifier /*dontResolveAlias*/, false, suppressInteropError) + targetSymbol := c.resolveESModuleSymbol(moduleSymbol, specifier, moduleSpecifier, suppressInteropError) if targetSymbol != nil { // Note: The empty string is a valid module export name: // @@ -14379,7 +14369,7 @@ func (c *Checker) getExportOfModule(symbol *ast.Symbol, nameText string, specifi exportSymbol := c.getExportsOfSymbol(symbol)[nameText] resolved := c.resolveSymbolEx(exportSymbol, dontResolveAlias) exportStarDeclaration := c.moduleSymbolLinks.Get(symbol).typeOnlyExportStarMap[nameText] - c.markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved /*overwriteEmpty*/, false, exportStarDeclaration, nameText) + c.markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportStarDeclaration) return resolved } return nil @@ -14547,30 +14537,30 @@ func (c *Checker) getTargetOfExportSpecifier(node *ast.Node, meaning ast.SymbolF default: resolved = c.resolveEntityName(name, meaning, false /*ignoreErrors*/, dontResolveAlias, nil /*location*/) } - c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil /*immediateTarget*/, resolved, false /*overwriteEmpty*/, nil, "") + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } -func (c *Checker) getTargetOfExportAssignment(node *ast.Node, dontResolveAlias bool) *ast.Symbol { - resolved := c.getTargetOfAliasLikeExpression(node.Expression(), dontResolveAlias) - c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil /*immediateTarget*/, resolved, false /*overwriteEmpty*/, nil, "") +func (c *Checker) getTargetOfExportAssignment(node *ast.Node) *ast.Symbol { + resolved := c.getTargetOfAliasLikeExpression(node.Expression()) + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } -func (c *Checker) getTargetOfBinaryExpression(node *ast.Node, dontResolveAlias bool) *ast.Symbol { - resolved := c.getTargetOfAliasLikeExpression(node.AsBinaryExpression().Right, dontResolveAlias) - c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil /*immediateTarget*/, resolved, false /*overwriteEmpty*/, nil, "") +func (c *Checker) getTargetOfBinaryExpression(node *ast.Node) *ast.Symbol { + resolved := c.getTargetOfAliasLikeExpression(node.AsBinaryExpression().Right) + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } -func (c *Checker) getTargetOfAliasLikeExpression(expression *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getTargetOfAliasLikeExpression(expression *ast.Node) *ast.Symbol { if ast.IsClassExpression(expression) { return c.checkExpressionCached(expression).symbol } if !ast.IsEntityName(expression) && !ast.IsEntityNameExpression(expression) { return nil } - aliasLike := c.resolveEntityName(expression, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, true /*ignoreErrors*/, dontResolveAlias, nil /*location*/) + aliasLike := c.resolveEntityName(expression, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, true /*ignoreErrors*/, true /*dontResolveAlias*/, nil /*location*/) if aliasLike != nil { return aliasLike } @@ -14578,20 +14568,20 @@ func (c *Checker) getTargetOfAliasLikeExpression(expression *ast.Node, dontResol return c.getResolvedSymbolOrNil(expression) } -func (c *Checker) getTargetOfNamespaceExportDeclaration(node *ast.Node, dontResolveAlias bool) *ast.Symbol { +func (c *Checker) getTargetOfNamespaceExportDeclaration(node *ast.Node) *ast.Symbol { if ast.CanHaveSymbol(node.Parent) { - resolved := c.resolveExternalModuleSymbol(node.Parent.Symbol(), dontResolveAlias) - c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil /*immediateTarget*/, resolved, false /*overwriteEmpty*/, nil, "") + resolved := c.resolveExternalModuleSymbol(node.Parent.Symbol(), true /*dontResolveAlias*/) + c.markSymbolOfAliasDeclarationIfTypeOnly(node, nil) return resolved } return nil } -func (c *Checker) getTargetOfAccessExpression(node *ast.Node, dontRecursivelyResolve bool) *ast.Symbol { +func (c *Checker) getTargetOfAccessExpression(node *ast.Node) *ast.Symbol { if ast.IsBinaryExpression(node.Parent) { expr := node.Parent.AsBinaryExpression() if expr.Left == node && expr.OperatorToken.Kind == ast.KindEqualsToken { - return c.getTargetOfAliasLikeExpression(expr.Right, dontRecursivelyResolve) + return c.getTargetOfAliasLikeExpression(expr.Right) } } return nil @@ -14650,40 +14640,22 @@ func getModuleSpecifierFromNode(node *ast.Node) *ast.Node { * must still be checked for a type-only marker, overwriting the previous negative result if found. */ -func (c *Checker) markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration *ast.Node, immediateTarget *ast.Symbol, finalTarget *ast.Symbol, overwriteEmpty bool, exportStarDeclaration *ast.Node, exportStarName string) bool { +func (c *Checker) markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration *ast.Node, exportStarDeclaration *ast.Node) bool { if aliasDeclaration == nil || !ast.IsDeclarationNode(aliasDeclaration) { return false } // If the declaration itself is type-only, mark it and return. No need to check what it resolves to. sourceSymbol := c.getSymbolOfDeclaration(aliasDeclaration) - if ast.IsTypeOnlyImportOrExportDeclaration(aliasDeclaration) { - links := c.aliasSymbolLinks.Get(sourceSymbol) + links := c.aliasSymbolLinks.Get(sourceSymbol) + if links.typeOnlyDeclaration == nil && ast.IsTypeOnlyImportOrExportDeclaration(aliasDeclaration) { links.typeOnlyDeclaration = aliasDeclaration return true } - if exportStarDeclaration != nil { - links := c.aliasSymbolLinks.Get(sourceSymbol) + if links.typeOnlyDeclaration == nil && exportStarDeclaration != nil { links.typeOnlyDeclaration = exportStarDeclaration - if sourceSymbol.Name != exportStarName { - links.typeOnlyExportStarName = exportStarName - } return true } - links := c.aliasSymbolLinks.Get(sourceSymbol) - return c.markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty) || c.markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty) -} - -func (c *Checker) markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks *AliasSymbolLinks, target *ast.Symbol, overwriteEmpty bool) bool { - if target != nil && (!aliasDeclarationLinks.typeOnlyDeclarationResolved || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration == nil) { - exportSymbol := core.OrElse(target.Exports[ast.InternalSymbolNameExportEquals], target) - aliasDeclarationLinks.typeOnlyDeclarationResolved = true - if typeOnly := core.Find(exportSymbol.Declarations, ast.IsTypeOnlyImportOrExportDeclaration); typeOnly != nil { - aliasDeclarationLinks.typeOnlyDeclaration = typeOnly - } else { - aliasDeclarationLinks.typeOnlyDeclaration = c.aliasSymbolLinks.Get(exportSymbol).typeOnlyDeclaration - } - } - return aliasDeclarationLinks.typeOnlyDeclaration != nil + return links.typeOnlyDeclaration != nil } func (c *Checker) resolveExternalModuleName(location *ast.Node, moduleReferenceExpression *ast.Node, ignoreErrors bool) *ast.Symbol { @@ -15117,15 +15089,19 @@ func (c *Checker) resolveExternalModuleSymbol(moduleSymbol *ast.Symbol, dontReso // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). -func (c *Checker) resolveESModuleSymbol(moduleSymbol *ast.Symbol, referencingLocation *ast.Node, dontResolveAlias bool, suppressInteropError bool) *ast.Symbol { - symbol := c.resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) - if !dontResolveAlias && symbol != nil { +func (c *Checker) resolveESModuleSymbol(moduleSymbol *ast.Symbol, node *ast.Node, moduleSpecifier *ast.Node, suppressInteropError bool) *ast.Symbol { + symbol := c.resolveExternalModuleSymbol(moduleSymbol, true /*dontResolveAlias*/) + if ast.IsNonLocalAlias(symbol, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace) { + // When the module has an export= with a pure alias, we transitively resolve and propagate any typeOnlyDeclaration + symbol = c.getMergedSymbol(c.resolveIndirectionAlias(c.getSymbolOfDeclaration(node), symbol)) + } + if symbol != nil { if !suppressInteropError && symbol.Flags&(ast.SymbolFlagsModule|ast.SymbolFlagsVariable) == 0 && ast.GetDeclarationOfKind(symbol, ast.KindSourceFile) == nil { compilerOptionName := core.IfElse(c.moduleKind >= core.ModuleKindES2015, "allowSyntheticDefaultImports", "esModuleInterop") - c.error(referencingLocation, diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName) + c.error(moduleSpecifier, diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName) return symbol } - referenceParent := referencingLocation.Parent + referenceParent := moduleSpecifier.Parent var namespaceImport *ast.Node if ast.IsImportDeclaration(referenceParent) { namespaceImport = ast.GetNamespaceDeclarationNode(referenceParent) @@ -15150,11 +15126,11 @@ func (c *Checker) resolveESModuleSymbol(moduleSymbol *ast.Symbol, referencingLoc core.ModuleKindNode20 <= c.moduleKind && c.moduleKind <= core.ModuleKindNodeNext && usageMode == core.ModuleKindCommonJS && c.program.GetImpliedNodeFormatForEmit(targetFile.AsSourceFile()) == core.ModuleKindESNext { - exportModuleDotExportsSymbol = c.getExportOfModule(symbol, ast.InternalSymbolNameModuleExports, namespaceImport, dontResolveAlias) + exportModuleDotExportsSymbol = c.getExportOfModule(symbol, ast.InternalSymbolNameModuleExports, namespaceImport, true /*dontResolveAlias*/) } if exportModuleDotExportsSymbol != nil { if !suppressInteropError && symbol.Flags&(ast.SymbolFlagsModule|ast.SymbolFlagsVariable) == 0 { - c.error(referencingLocation, diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, "esModuleInterop") + c.error(moduleSpecifier, diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, "esModuleInterop") } if c.compilerOptions.GetESModuleInterop() && c.hasSignatures(typ) { return c.cloneTypeAsModuleType(exportModuleDotExportsSymbol, typ, referenceParent) @@ -15286,35 +15262,35 @@ func (c *Checker) cloneTypeAsModuleType(symbol *ast.Symbol, moduleType *Type, re return result } -func (c *Checker) getTargetOfAliasDeclaration(node *ast.Node, dontRecursivelyResolve bool) *ast.Symbol { +func (c *Checker) getTargetOfAliasDeclaration(node *ast.Node) *ast.Symbol { if node == nil { return nil } switch node.Kind { case ast.KindImportEqualsDeclaration, ast.KindVariableDeclaration: - return c.getTargetOfImportEqualsDeclaration(node, dontRecursivelyResolve) + return c.getTargetOfImportEqualsDeclaration(node) case ast.KindImportClause: - return c.getTargetOfImportClause(node, dontRecursivelyResolve) + return c.getTargetOfImportClause(node) case ast.KindNamespaceImport: - return c.getTargetOfNamespaceImport(node, dontRecursivelyResolve) + return c.getTargetOfNamespaceImport(node) case ast.KindNamespaceExport: - return c.getTargetOfNamespaceExport(node, dontRecursivelyResolve) + return c.getTargetOfNamespaceExport(node) case ast.KindImportSpecifier, ast.KindBindingElement: - return c.getTargetOfImportSpecifier(node, dontRecursivelyResolve) + return c.getTargetOfImportSpecifier(node) case ast.KindExportSpecifier: - return c.getTargetOfExportSpecifier(node, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, dontRecursivelyResolve) + return c.getTargetOfExportSpecifier(node, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, true /*dontRecursivelyResolve*/) case ast.KindExportAssignment, ast.KindJSExportAssignment: - return c.getTargetOfExportAssignment(node, dontRecursivelyResolve) + return c.getTargetOfExportAssignment(node) case ast.KindBinaryExpression: - return c.getTargetOfBinaryExpression(node, dontRecursivelyResolve) + return c.getTargetOfBinaryExpression(node) case ast.KindNamespaceExportDeclaration: - return c.getTargetOfNamespaceExportDeclaration(node, dontRecursivelyResolve) + return c.getTargetOfNamespaceExportDeclaration(node) case ast.KindShorthandPropertyAssignment: - return c.resolveEntityName(node.AsShorthandPropertyAssignment().Name(), ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, true /*ignoreErrors*/, dontRecursivelyResolve, nil /*location*/) + return c.resolveEntityName(node.AsShorthandPropertyAssignment().Name(), ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, true /*ignoreErrors*/, true /*dontRecursivelyResolve*/, nil /*location*/) case ast.KindPropertyAssignment: - return c.getTargetOfAliasLikeExpression(node.Initializer(), dontRecursivelyResolve) + return c.getTargetOfAliasLikeExpression(node.Initializer()) case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression: - return c.getTargetOfAccessExpression(node, dontRecursivelyResolve) + return c.getTargetOfAccessExpression(node) } panic("Unhandled case in getTargetOfAliasDeclaration: " + node.Kind.String()) } @@ -15370,7 +15346,7 @@ func (c *Checker) resolveEntityName(name *ast.Node, meaning ast.SymbolFlags, ign (symbol.Flags&ast.SymbolFlagsAlias != 0 || name.Parent != nil && name.Parent.Kind == ast.KindExportAssignment || name.Parent != nil && name.Parent.Kind == ast.KindJSExportAssignment) { - c.markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, nil /*finalTarget*/, true /*overwriteEmpty*/, nil, "") + c.markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), nil) } if symbol.Flags&meaning == 0 && !dontResolveAlias && symbol.Flags&ast.SymbolFlagsAlias != 0 { return c.resolveAlias(symbol) @@ -15793,6 +15769,9 @@ func (c *Checker) ResolveAlias(symbol *ast.Symbol) (*ast.Symbol, bool) { return resolved, resolved != c.unknownSymbol } +// Resolve an alias symbol to the first target symbol in the resolution chain that includes some other +// meaning. Pure aliases are eagerly resolved and any type-only markers are back-propagated to the original +// symbol. The function panics if the argument is not a symbol with an alias meaning. func (c *Checker) resolveAlias(symbol *ast.Symbol) *ast.Symbol { if symbol.Flags&ast.SymbolFlagsAlias == 0 { panic("Should only get alias here") @@ -15806,7 +15785,12 @@ func (c *Checker) resolveAlias(symbol *ast.Symbol) *ast.Symbol { if node == nil { panic("Unexpected nil in resolveAlias for symbol: " + c.symbolToString(symbol)) } - links.aliasTarget = core.OrElse(c.getTargetOfAliasDeclaration(node, false /*dontRecursivelyResolve*/), c.unknownSymbol) + target := c.getTargetOfAliasDeclaration(node) + if ast.IsNonLocalAlias(target, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace) { + // When the target is a pure alias, we transitively resolve and propagate any typeOnlyDeclaration + target = c.resolveIndirectionAlias(symbol, target) + } + links.aliasTarget = core.OrElse(target, c.unknownSymbol) if !c.popTypeResolution() { c.error(node, diagnostics.Circular_definition_of_import_alias_0, c.symbolToString(symbol)) links.aliasTarget = c.unknownSymbol @@ -15815,6 +15799,16 @@ func (c *Checker) resolveAlias(symbol *ast.Symbol) *ast.Symbol { return links.aliasTarget } +func (c *Checker) resolveIndirectionAlias(source *ast.Symbol, target *ast.Symbol) *ast.Symbol { + result := c.resolveAlias(target) + if targetLinks := c.aliasSymbolLinks.Get(target); targetLinks.typeOnlyDeclaration != nil { + if sourceLinks := c.aliasSymbolLinks.Get(source); sourceLinks.typeOnlyDeclaration == nil { + sourceLinks.typeOnlyDeclaration = targetLinks.typeOnlyDeclaration + } + } + return result +} + func (c *Checker) tryResolveAlias(symbol *ast.Symbol) *ast.Symbol { links := c.aliasSymbolLinks.Get(symbol) if links.aliasTarget != nil || c.findResolutionCycleStartIndex(symbol, TypeSystemPropertyNameAliasTarget) < 0 { @@ -15880,43 +15874,24 @@ func (c *Checker) getSymbolFlags(symbol *ast.Symbol) ast.SymbolFlags { } func (c *Checker) getSymbolFlagsEx(symbol *ast.Symbol, excludeTypeOnlyMeanings bool, excludeLocalMeanings bool) ast.SymbolFlags { - var typeOnlyDeclaration *ast.Node - if excludeTypeOnlyMeanings { - typeOnlyDeclaration = c.getTypeOnlyAliasDeclaration(symbol) - } - typeOnlyDeclarationIsExportStar := typeOnlyDeclaration != nil && ast.IsExportDeclaration(typeOnlyDeclaration) - var typeOnlyResolution *ast.Symbol - if typeOnlyDeclaration != nil { - if typeOnlyDeclarationIsExportStar { - moduleSpecifier := typeOnlyDeclaration.ModuleSpecifier() - typeOnlyResolution = c.resolveExternalModuleName(moduleSpecifier, moduleSpecifier /*ignoreErrors*/, true) - } else { - typeOnlyResolution = c.resolveAlias(typeOnlyDeclaration.Symbol()) - } - } - var typeOnlyExportStarTargets ast.SymbolTable - if typeOnlyDeclarationIsExportStar && typeOnlyResolution != nil { - typeOnlyExportStarTargets = c.getExportsOfModule(typeOnlyResolution) - } + var seenSymbols collections.Set[*ast.Symbol] var flags ast.SymbolFlags if !excludeLocalMeanings { flags = symbol.Flags } - var seenSymbols collections.Set[*ast.Symbol] for symbol.Flags&ast.SymbolFlagsAlias != 0 { - target := c.getExportSymbolOfValueSymbolIfExported(c.resolveAlias(symbol)) - if !typeOnlyDeclarationIsExportStar && target == typeOnlyResolution || typeOnlyExportStarTargets[target.Name] == target { + if excludeTypeOnlyMeanings && c.getTypeOnlyAliasDeclaration(symbol) != nil { break } + target := c.getExportSymbolOfValueSymbolIfExported(c.resolveAlias(symbol)) if target == c.unknownSymbol { return ast.SymbolFlagsAll } - // Optimizations - try to avoid creating or adding to - // `seenSymbols` if possible - if target == symbol || seenSymbols.Has(target) { - break - } if target.Flags&ast.SymbolFlagsAlias != 0 { + // Optimization - try to avoid creating or adding to `seenSymbols` if possible + if target == symbol || seenSymbols.Has(target) { + break + } if seenSymbols.Len() == 0 { seenSymbols.Add(symbol) } @@ -18042,7 +18017,7 @@ func (c *Checker) getTypeOfAlias(symbol *ast.Symbol) *Type { return c.errorType } targetSymbol := c.resolveAlias(symbol) - exportSymbol := c.getTargetOfAliasDeclaration(c.getDeclarationOfAliasSymbol(symbol), true /*dontRecursivelyResolve*/) + exportSymbol := c.getTargetOfAliasDeclaration(c.getDeclarationOfAliasSymbol(symbol)) // It only makes sense to get the type of a value symbol. If the result of resolving // the alias is not a value, then it has no type. To get the type associated with a // type symbol, call getDeclaredTypeOfSymbol. @@ -30738,7 +30713,7 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast if importEqualsDeclaration == nil { panic("ImportEqualsDeclaration should be defined") } - return c.getSymbolOfPartOfRightHandSideOfImportEquals(name, true /*dontResolveAlias*/) + return c.getSymbolOfPartOfRightHandSideOfImportEquals(name) } if ast.IsEntityName(name) { diff --git a/pkg/checker/types.go b/pkg/checker/types.go index f429ed7fa..da5e2e9d8 100644 --- a/pkg/checker/types.go +++ b/pkg/checker/types.go @@ -146,12 +146,10 @@ type DeferredSymbolLinks struct { // Links for alias symbols type AliasSymbolLinks struct { - immediateTarget *ast.Symbol // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead. - aliasTarget *ast.Symbol // Resolved (non-alias) target of an alias - referenced bool // True if alias symbol has been referenced as a value that can be emitted - typeOnlyDeclarationResolved bool // True when typeOnlyDeclaration resolution in process - typeOnlyDeclaration *ast.Node // First resolved alias declaration that makes the symbol only usable in type constructs - typeOnlyExportStarName string // Set to the name of the symbol re-exported by an 'export type *' declaration, when different from the symbol name + immediateTarget *ast.Symbol // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead. + aliasTarget *ast.Symbol // Resolved (non-alias) target of an alias + referenced bool // True if alias symbol has been referenced as a value that can be emitted + typeOnlyDeclaration *ast.Node // First resolved alias declaration that makes the symbol only usable in type constructs } // Links for module symbols diff --git a/pkg/compiler/fileloader.go b/pkg/compiler/fileloader.go index 870b42c08..7aea6d52e 100644 --- a/pkg/compiler/fileloader.go +++ b/pkg/compiler/fileloader.go @@ -129,128 +129,12 @@ func processAllProgramFiles( } loader.filesParser.parse(&loader, loader.rootTasks) + // Clear out loader and host to ensure its not used post program creation loader.projectReferenceFileMapper.loader = nil loader.projectReferenceFileMapper.host = nil - totalFileCount := int(loader.totalFileCount.Load()) - libFileCount := int(loader.libFileCount.Load()) - - var missingFiles []string - files := make([]*ast.SourceFile, 0, totalFileCount-libFileCount) - libFiles := make([]*ast.SourceFile, 0, totalFileCount) // totalFileCount here since we append files to it later to construct the final list - - filesByPath := make(map[tspath.Path]*ast.SourceFile, totalFileCount) - loader.includeProcessor.fileIncludeReasons = make(map[tspath.Path][]*FileIncludeReason, totalFileCount) - var outputFileToProjectReferenceSource map[tspath.Path]string - if !opts.canUseProjectReferenceSource() { - outputFileToProjectReferenceSource = make(map[tspath.Path]string, totalFileCount) - } - resolvedModules := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule], totalFileCount+1) - typeResolutionsInFile := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], totalFileCount) - sourceFileMetaDatas := make(map[tspath.Path]ast.SourceFileMetaData, totalFileCount) - var jsxRuntimeImportSpecifiers map[tspath.Path]*jsxRuntimeImportSpecifier - var importHelpersImportSpecifiers map[tspath.Path]*ast.Node - var sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path] - libFilesMap := make(map[tspath.Path]*LibFile, libFileCount) - - loader.filesParser.collect(&loader, loader.rootTasks, func(task *parseTask) { - if task.redirectedParseTask != nil { - if !opts.canUseProjectReferenceSource() { - outputFileToProjectReferenceSource[task.redirectedParseTask.path] = task.FileName() - } - return - } - - if task.isForAutomaticTypeDirective { - typeResolutionsInFile[task.path] = task.typeResolutionsInFile - return - } - file := task.file - path := task.path - if file == nil { - // !!! sheetal file preprocessing diagnostic explaining getSourceFileFromReferenceWorker - missingFiles = append(missingFiles, task.normalizedFilePath) - return - } - - // !!! sheetal todo porting file case errors - // if _, ok := filesByPath[path]; ok { - // Check if it differs only in drive letters its ok to ignore that error: - // const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); - // const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); - // if (checkedAbsolutePath !== inputAbsolutePath) { - // reportFileNamesDifferOnlyInCasingError(fileName, file, reason); - // } - // } else if loader.comparePathsOptions.UseCaseSensitiveFileNames { - // pathIgnoreCase := tspath.ToPath(file.FileName(), loader.comparePathsOptions.CurrentDirectory, false) - // // for case-sensitsive file systems check if we've already seen some file with similar filename ignoring case - // if _, ok := filesByNameIgnoreCase[pathIgnoreCase]; ok { - // reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); - // } else { - // filesByNameIgnoreCase[pathIgnoreCase] = file - // } - // } - - if task.libFile != nil { - libFiles = append(libFiles, file) - libFilesMap[path] = task.libFile - } else { - files = append(files, file) - } - filesByPath[path] = file - resolvedModules[path] = task.resolutionsInFile - typeResolutionsInFile[path] = task.typeResolutionsInFile - sourceFileMetaDatas[path] = task.metadata - - if task.jsxRuntimeImportSpecifier != nil { - if jsxRuntimeImportSpecifiers == nil { - jsxRuntimeImportSpecifiers = make(map[tspath.Path]*jsxRuntimeImportSpecifier, totalFileCount) - } - jsxRuntimeImportSpecifiers[path] = task.jsxRuntimeImportSpecifier - } - if task.importHelpersImportSpecifier != nil { - if importHelpersImportSpecifiers == nil { - importHelpersImportSpecifiers = make(map[tspath.Path]*ast.Node, totalFileCount) - } - importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier - } - if task.fromExternalLibrary { - sourceFilesFoundSearchingNodeModules.Add(path) - } - }) - loader.sortLibs(libFiles) - - allFiles := append(libFiles, files...) - - keys := slices.Collect(loader.pathForLibFileResolutions.Keys()) - slices.Sort(keys) - for _, key := range keys { - value, _ := loader.pathForLibFileResolutions.Load(key) - resolvedModules[key] = module.ModeAwareCache[*module.ResolvedModule]{ - module.ModeAwareCacheKey{Name: value.libraryName, Mode: core.ModuleKindCommonJS}: value.resolution, - } - for _, trace := range value.trace { - opts.Host.Trace(trace.Message, trace.Args...) - } - } - - return processedFiles{ - resolver: loader.resolver, - files: allFiles, - filesByPath: filesByPath, - projectReferenceFileMapper: loader.projectReferenceFileMapper, - resolvedModules: resolvedModules, - typeResolutionsInFile: typeResolutionsInFile, - sourceFileMetaDatas: sourceFileMetaDatas, - jsxRuntimeImportSpecifiers: jsxRuntimeImportSpecifiers, - importHelpersImportSpecifiers: importHelpersImportSpecifiers, - sourceFilesFoundSearchingNodeModules: sourceFilesFoundSearchingNodeModules, - libFiles: libFilesMap, - missingFiles: missingFiles, - includeProcessor: loader.includeProcessor, - outputFileToProjectReferenceSource: outputFileToProjectReferenceSource, - } + return loader.filesParser.getProcessedFiles(&loader) } func (p *fileLoader) toPath(file string) tspath.Path { @@ -466,11 +350,10 @@ func (p *fileLoader) resolveTypeReferenceDirectives(t *parseTask) { if resolved.IsResolved() { t.addSubTask(resolvedRef{ - fileName: resolved.ResolvedFileName, - increaseDepth: resolved.IsExternalLibraryImport, - elideOnDepth: false, - isFromExternalLibrary: resolved.IsExternalLibraryImport, - includeReason: includeReason, + fileName: resolved.ResolvedFileName, + increaseDepth: resolved.IsExternalLibraryImport, + elideOnDepth: false, + includeReason: includeReason, }, nil) } else { p.includeProcessor.addProcessingDiagnostic(&processingDiagnostic{ @@ -566,10 +449,9 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) { if shouldAddFile { t.addSubTask(resolvedRef{ - fileName: resolvedFileName, - increaseDepth: resolvedModule.IsExternalLibraryImport, - elideOnDepth: isJsFileFromNodeModules, - isFromExternalLibrary: resolvedModule.IsExternalLibraryImport, + fileName: resolvedFileName, + increaseDepth: resolvedModule.IsExternalLibraryImport, + elideOnDepth: isJsFileFromNodeModules, includeReason: &FileIncludeReason{ kind: fileIncludeKindImport, data: &referencedFileData{ diff --git a/pkg/compiler/filesparser.go b/pkg/compiler/filesparser.go index 24c4d25ac..ed3fdc2c8 100644 --- a/pkg/compiler/filesparser.go +++ b/pkg/compiler/filesparser.go @@ -2,6 +2,7 @@ package compiler import ( "math" + "slices" "sync" "github.com/buke/typescript-go-internal/pkg/ast" @@ -20,6 +21,7 @@ type parseTask struct { redirectedParseTask *parseTask subTasks []*parseTask loaded bool + startedSubTasks bool isForAutomaticTypeDirective bool includeReason *FileIncludeReason @@ -31,12 +33,9 @@ type parseTask struct { resolutionDiagnostics []*ast.Diagnostic importHelpersImportSpecifier *ast.Node jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier - increaseDepth bool - elideOnDepth bool - // Track if this file is from an external library (node_modules) - // This mirrors the TypeScript currentNodeModulesDepth > 0 check - fromExternalLibrary bool + increaseDepth bool + elideOnDepth bool loadedTask *parseTask allIncludeReasons []*FileIncludeReason @@ -50,14 +49,8 @@ func (t *parseTask) Path() tspath.Path { return t.path } -func (t *parseTask) isRoot() bool { - // Intentionally not checking t.includeReason != nil to ensure we can catch cases for missing include reason - return !t.isForAutomaticTypeDirective && (t.includeReason.kind == fileIncludeKindRootFile || t.includeReason.kind == fileIncludeKindLibFile) -} - func (t *parseTask) load(loader *fileLoader) { t.loaded = true - t.path = loader.toPath(t.normalizedFilePath) if t.isForAutomaticTypeDirective { t.loadAutomaticTypeDirectives(loader) return @@ -119,10 +112,9 @@ func (t *parseTask) load(loader *fileLoader) { func (t *parseTask) redirect(loader *fileLoader, fileName string) { t.redirectedParseTask = &parseTask{ - normalizedFilePath: tspath.NormalizePath(fileName), - libFile: t.libFile, - fromExternalLibrary: t.fromExternalLibrary, - includeReason: t.includeReason, + normalizedFilePath: tspath.NormalizePath(fileName), + libFile: t.libFile, + includeReason: t.includeReason, } // increaseDepth and elideOnDepth are not copied to redirects, otherwise their depth would be double counted. t.subTasks = []*parseTask{t.redirectedParseTask} @@ -138,132 +130,262 @@ func (t *parseTask) loadAutomaticTypeDirectives(loader *fileLoader) { } type resolvedRef struct { - fileName string - increaseDepth bool - elideOnDepth bool - isFromExternalLibrary bool - includeReason *FileIncludeReason + fileName string + increaseDepth bool + elideOnDepth bool + includeReason *FileIncludeReason } func (t *parseTask) addSubTask(ref resolvedRef, libFile *LibFile) { normalizedFilePath := tspath.NormalizePath(ref.fileName) subTask := &parseTask{ - normalizedFilePath: normalizedFilePath, - libFile: libFile, - increaseDepth: ref.increaseDepth, - elideOnDepth: ref.elideOnDepth, - fromExternalLibrary: ref.isFromExternalLibrary, - includeReason: ref.includeReason, + normalizedFilePath: normalizedFilePath, + libFile: libFile, + increaseDepth: ref.increaseDepth, + elideOnDepth: ref.elideOnDepth, + includeReason: ref.includeReason, } t.subTasks = append(t.subTasks, subTask) } type filesParser struct { - wg core.WorkGroup - tasksByFileName collections.SyncMap[string, *queuedParseTask] - maxDepth int + wg core.WorkGroup + taskDataByPath collections.SyncMap[tspath.Path, *parseTaskData] + maxDepth int } -type queuedParseTask struct { - task *parseTask - mu sync.Mutex - lowestDepth int - fromExternalLibrary bool +type parseTaskData struct { + // map of tasks by file casing + tasks map[string]*parseTask + mu sync.Mutex + lowestDepth int + startedSubTasks bool } func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { - w.start(loader, tasks, 0, false) + w.start(loader, tasks, 0) w.wg.RunAndWait() } -func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, isFromExternalLibrary bool) { +func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { for i, task := range tasks { - taskIsFromExternalLibrary := isFromExternalLibrary || task.fromExternalLibrary - newTask := &queuedParseTask{task: task, lowestDepth: math.MaxInt} - loadedTask, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), newTask) - task = loadedTask.task - if loaded { - tasks[i].loadedTask = task - // Add in the loaded task's external-ness. - taskIsFromExternalLibrary = taskIsFromExternalLibrary || task.fromExternalLibrary - } + task.path = loader.toPath(task.normalizedFilePath) + data, loaded := w.taskDataByPath.LoadOrStore(task.path, &parseTaskData{ + tasks: map[string]*parseTask{task.normalizedFilePath: task}, + lowestDepth: math.MaxInt, + }) w.wg.Queue(func() { - loadedTask.mu.Lock() - defer loadedTask.mu.Unlock() + data.mu.Lock() + defer data.mu.Unlock() startSubtasks := false - - currentDepth := depth - if task.increaseDepth { - currentDepth++ + if loaded { + if existingTask, ok := data.tasks[task.normalizedFilePath]; ok { + tasks[i].loadedTask = existingTask + } else { + data.tasks[task.normalizedFilePath] = task + // This is new task for file name - so load subtasks if there was loading for any other casing + startSubtasks = data.startedSubTasks + } } - if currentDepth < loadedTask.lowestDepth { + + currentDepth := core.IfElse(task.increaseDepth, depth+1, depth) + if currentDepth < data.lowestDepth { // If we're seeing this task at a lower depth than before, // reprocess its subtasks to ensure they are loaded. - loadedTask.lowestDepth = currentDepth - startSubtasks = true - } - - if !task.isRoot() && taskIsFromExternalLibrary && !loadedTask.fromExternalLibrary { - // If we're seeing this task now as an external library, - // reprocess its subtasks to ensure they are also marked as external. - loadedTask.fromExternalLibrary = true + data.lowestDepth = currentDepth startSubtasks = true + data.startedSubTasks = true } if task.elideOnDepth && currentDepth > w.maxDepth { return } - if !task.loaded { - task.load(loader) - } - - if startSubtasks { - w.start(loader, task.subTasks, loadedTask.lowestDepth, loadedTask.fromExternalLibrary) + for _, taskByFileName := range data.tasks { + loadSubTasks := startSubtasks + if !taskByFileName.loaded { + taskByFileName.load(loader) + if taskByFileName.redirectedParseTask != nil { + // Always load redirected task + loadSubTasks = true + data.startedSubTasks = true + } + } + if !taskByFileName.startedSubTasks && loadSubTasks { + taskByFileName.startedSubTasks = true + w.start(loader, taskByFileName.subTasks, data.lowestDepth) + } } }) } } -func (w *filesParser) collect(loader *fileLoader, tasks []*parseTask, iterate func(*parseTask)) { - // Mark all tasks we saw as external after the fact. - w.tasksByFileName.Range(func(key string, value *queuedParseTask) bool { - if value.fromExternalLibrary { - value.task.fromExternalLibrary = true - } - return true - }) - w.collectWorker(loader, tasks, iterate, collections.Set[*parseTask]{}) -} +func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { + totalFileCount := int(loader.totalFileCount.Load()) + libFileCount := int(loader.libFileCount.Load()) + + var missingFiles []string + files := make([]*ast.SourceFile, 0, totalFileCount-libFileCount) + libFiles := make([]*ast.SourceFile, 0, totalFileCount) // totalFileCount here since we append files to it later to construct the final list + + filesByPath := make(map[tspath.Path]*ast.SourceFile, totalFileCount) + // stores 'filename -> file association' ignoring case + // used to track cases when two file names differ only in casing + var tasksSeenByNameIgnoreCase map[string]*parseTask + if loader.comparePathsOptions.UseCaseSensitiveFileNames { + tasksSeenByNameIgnoreCase = make(map[string]*parseTask, totalFileCount) + } -func (w *filesParser) collectWorker(loader *fileLoader, tasks []*parseTask, iterate func(*parseTask), seen collections.Set[*parseTask]) { - for _, task := range tasks { - // Exclude automatic type directive tasks from include reason processing, - // as these are internal implementation details and should not contribute - // to the reasons for including files. - if task.redirectedParseTask == nil && !task.isForAutomaticTypeDirective { + loader.includeProcessor.fileIncludeReasons = make(map[tspath.Path][]*FileIncludeReason, totalFileCount) + var outputFileToProjectReferenceSource map[tspath.Path]string + if !loader.opts.canUseProjectReferenceSource() { + outputFileToProjectReferenceSource = make(map[tspath.Path]string, totalFileCount) + } + resolvedModules := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule], totalFileCount+1) + typeResolutionsInFile := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], totalFileCount) + sourceFileMetaDatas := make(map[tspath.Path]ast.SourceFileMetaData, totalFileCount) + var jsxRuntimeImportSpecifiers map[tspath.Path]*jsxRuntimeImportSpecifier + var importHelpersImportSpecifiers map[tspath.Path]*ast.Node + var sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path] + libFilesMap := make(map[tspath.Path]*LibFile, libFileCount) + + var collectFiles func(tasks []*parseTask, seen map[*parseTaskData]string) + collectFiles = func(tasks []*parseTask, seen map[*parseTaskData]string) { + for _, task := range tasks { includeReason := task.includeReason - if task.loadedTask != nil { - task = task.loadedTask + // Exclude automatic type directive tasks from include reason processing, + // as these are internal implementation details and should not contribute + // to the reasons for including files. + if task.redirectedParseTask == nil && !task.isForAutomaticTypeDirective { + if task.loadedTask != nil { + task = task.loadedTask + } + w.addIncludeReason(loader, task, includeReason) + } + data, _ := w.taskDataByPath.Load(task.path) + if !task.loaded { + continue + } + + // ensure we only walk each task once + if checkedName, ok := seen[data]; ok { + if !loader.opts.Config.CompilerOptions().ForceConsistentCasingInFileNames.IsFalse() { + // Check if it differs only in drive letters its ok to ignore that error: + checkedAbsolutePath := tspath.GetNormalizedAbsolutePathWithoutRoot(checkedName, loader.comparePathsOptions.CurrentDirectory) + inputAbsolutePath := tspath.GetNormalizedAbsolutePathWithoutRoot(task.normalizedFilePath, loader.comparePathsOptions.CurrentDirectory) + if checkedAbsolutePath != inputAbsolutePath { + loader.includeProcessor.addProcessingDiagnosticsForFileCasing(task.path, checkedName, task.normalizedFilePath, includeReason) + } + } + continue + } else { + seen[data] = task.normalizedFilePath + } + + if tasksSeenByNameIgnoreCase != nil { + pathLowerCase := tspath.ToFileNameLowerCase(string(task.path)) + if taskByIgnoreCase, ok := tasksSeenByNameIgnoreCase[pathLowerCase]; ok { + loader.includeProcessor.addProcessingDiagnosticsForFileCasing(taskByIgnoreCase.path, taskByIgnoreCase.normalizedFilePath, task.normalizedFilePath, includeReason) + } else { + tasksSeenByNameIgnoreCase[pathLowerCase] = task + } + } + + for _, trace := range task.typeResolutionsTrace { + loader.opts.Host.Trace(trace.Message, trace.Args...) + } + for _, trace := range task.resolutionsTrace { + loader.opts.Host.Trace(trace.Message, trace.Args...) + } + if subTasks := task.subTasks; len(subTasks) > 0 { + collectFiles(subTasks, seen) + } + + // Exclude automatic type directive tasks from include reason processing, + // as these are internal implementation details and should not contribute + // to the reasons for including files. + if task.redirectedParseTask != nil { + if !loader.opts.canUseProjectReferenceSource() { + outputFileToProjectReferenceSource[task.redirectedParseTask.path] = task.FileName() + } + continue + } + + if task.isForAutomaticTypeDirective { + typeResolutionsInFile[task.path] = task.typeResolutionsInFile + continue + } + file := task.file + path := task.path + if file == nil { + // !!! sheetal file preprocessing diagnostic explaining getSourceFileFromReferenceWorker + missingFiles = append(missingFiles, task.normalizedFilePath) + continue + } + + if task.libFile != nil { + libFiles = append(libFiles, file) + libFilesMap[path] = task.libFile + } else { + files = append(files, file) + } + filesByPath[path] = file + resolvedModules[path] = task.resolutionsInFile + typeResolutionsInFile[path] = task.typeResolutionsInFile + sourceFileMetaDatas[path] = task.metadata + + if task.jsxRuntimeImportSpecifier != nil { + if jsxRuntimeImportSpecifiers == nil { + jsxRuntimeImportSpecifiers = make(map[tspath.Path]*jsxRuntimeImportSpecifier, totalFileCount) + } + jsxRuntimeImportSpecifiers[path] = task.jsxRuntimeImportSpecifier + } + if task.importHelpersImportSpecifier != nil { + if importHelpersImportSpecifiers == nil { + importHelpersImportSpecifiers = make(map[tspath.Path]*ast.Node, totalFileCount) + } + importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier + } + if data.lowestDepth > 0 { + sourceFilesFoundSearchingNodeModules.Add(path) } - w.addIncludeReason(loader, task, includeReason) - } - // ensure we only walk each task once - if !task.loaded || !seen.AddIfAbsent(task) { - continue } - for _, trace := range task.typeResolutionsTrace { - loader.opts.Host.Trace(trace.Message, trace.Args...) + } + + collectFiles(loader.rootTasks, make(map[*parseTaskData]string, totalFileCount)) + loader.sortLibs(libFiles) + + allFiles := append(libFiles, files...) + + keys := slices.Collect(loader.pathForLibFileResolutions.Keys()) + slices.Sort(keys) + for _, key := range keys { + value, _ := loader.pathForLibFileResolutions.Load(key) + resolvedModules[key] = module.ModeAwareCache[*module.ResolvedModule]{ + module.ModeAwareCacheKey{Name: value.libraryName, Mode: core.ModuleKindCommonJS}: value.resolution, } - for _, trace := range task.resolutionsTrace { + for _, trace := range value.trace { loader.opts.Host.Trace(trace.Message, trace.Args...) } - if subTasks := task.subTasks; len(subTasks) > 0 { - w.collectWorker(loader, subTasks, iterate, seen) - } - iterate(task) + } + + return processedFiles{ + resolver: loader.resolver, + files: allFiles, + filesByPath: filesByPath, + projectReferenceFileMapper: loader.projectReferenceFileMapper, + resolvedModules: resolvedModules, + typeResolutionsInFile: typeResolutionsInFile, + sourceFileMetaDatas: sourceFileMetaDatas, + jsxRuntimeImportSpecifiers: jsxRuntimeImportSpecifiers, + importHelpersImportSpecifiers: importHelpersImportSpecifiers, + sourceFilesFoundSearchingNodeModules: sourceFilesFoundSearchingNodeModules, + libFiles: libFilesMap, + missingFiles: missingFiles, + includeProcessor: loader.includeProcessor, + outputFileToProjectReferenceSource: outputFileToProjectReferenceSource, } } diff --git a/pkg/compiler/includeprocessor.go b/pkg/compiler/includeprocessor.go index 4c0eb4365..ab096ad1f 100644 --- a/pkg/compiler/includeprocessor.go +++ b/pkg/compiler/includeprocessor.go @@ -1,6 +1,7 @@ package compiler import ( + "slices" "sync" "github.com/buke/typescript-go-internal/pkg/ast" @@ -59,6 +60,32 @@ func (i *includeProcessor) addProcessingDiagnostic(d ...*processingDiagnostic) { i.processingDiagnostics = append(i.processingDiagnostics, d...) } +func (i *includeProcessor) addProcessingDiagnosticsForFileCasing(file tspath.Path, existingCasing string, currentCasing string, reason *FileIncludeReason) { + if !reason.isReferencedFile() && slices.ContainsFunc(i.fileIncludeReasons[file], func(r *FileIncludeReason) bool { + return r.isReferencedFile() + }) { + i.addProcessingDiagnostic(&processingDiagnostic{ + kind: processingDiagnosticKindExplainingFileInclude, + data: &includeExplainingDiagnostic{ + file: file, + diagnosticReason: reason, + message: diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + args: []any{existingCasing, currentCasing}, + }, + }) + } else { + i.addProcessingDiagnostic(&processingDiagnostic{ + kind: processingDiagnosticKindExplainingFileInclude, + data: &includeExplainingDiagnostic{ + file: file, + diagnosticReason: reason, + message: diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, + args: []any{currentCasing, existingCasing}, + }, + }) + } +} + func (i *includeProcessor) getReferenceLocation(r *FileIncludeReason, program *Program) *referenceFileLocation { if existing, ok := i.reasonToReferenceLocation.Load(r); ok { return existing diff --git a/pkg/compiler/processingDiagnostic.go b/pkg/compiler/processingDiagnostic.go index 3c170d6c2..d21736073 100644 --- a/pkg/compiler/processingDiagnostic.go +++ b/pkg/compiler/processingDiagnostic.go @@ -79,7 +79,7 @@ func (d *processingDiagnostic) createDiagnosticExplainingFile(program *Program) processRelatedInfo := func(includeReason *FileIncludeReason) { if preferredLocation == nil && includeReason.isReferencedFile() && !program.includeProcessor.getReferenceLocation(includeReason, program).isSynthetic { preferredLocation = includeReason - } else { + } else if preferredLocation != includeReason { info := program.includeProcessor.getRelatedInfo(includeReason, program) if info != nil { relatedInfo = append(relatedInfo, info) diff --git a/pkg/compiler/program.go b/pkg/compiler/program.go index dffc66089..27b473338 100644 --- a/pkg/compiler/program.go +++ b/pkg/compiler/program.go @@ -634,6 +634,10 @@ func (p *Program) verifyCompilerOptions() { } } + if options.TsBuildInfoFile == "" && options.Incremental.IsTrue() && options.ConfigFilePath == "" { + createCompilerOptionsDiagnostic(diagnostics.Option_incremental_is_only_valid_with_a_known_configuration_file_like_tsconfig_json_or_when_tsBuildInfoFile_is_explicitly_provided) + } + p.verifyProjectReferences() if options.Composite.IsTrue() { @@ -1041,15 +1045,12 @@ func (p *Program) getSemanticDiagnosticsForFileNotFilter(ctx context.Context, so defer done() } diags := slices.Clip(sourceFile.BindDiagnostics()) - // Ask for diags from all checkers; checking one file may add diagnostics to other files. // These are deduplicated later. checkerDiags := make([][]*ast.Diagnostic, p.checkerPool.Count()) p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) { if sourceFile == nil || checker == fileChecker { checkerDiags[idx] = checker.GetDiagnostics(ctx, sourceFile) - } else { - checkerDiags[idx] = checker.GetDiagnosticsWithoutCheck(sourceFile) } }) if ctx.Err() != nil { diff --git a/pkg/diagnostics/diagnostics_generated.go b/pkg/diagnostics/diagnostics_generated.go index 7710a0f87..2f9700f81 100644 --- a/pkg/diagnostics/diagnostics_generated.go +++ b/pkg/diagnostics/diagnostics_generated.go @@ -2288,7 +2288,7 @@ var Unknown_build_option_0 = &Message{code: 5072, category: CategoryError, key: var Build_option_0_requires_a_value_of_type_1 = &Message{code: 5073, category: CategoryError, key: "Build_option_0_requires_a_value_of_type_1_5073", text: "Build option '{0}' requires a value of type {1}."} -var Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified = &Message{code: 5074, category: CategoryError, key: "Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBui_5074", text: "Option '--incremental' can only be specified using tsconfig, emitting to single file or when option '--tsBuildInfoFile' is specified."} +var Option_incremental_is_only_valid_with_a_known_configuration_file_like_tsconfig_json_or_when_tsBuildInfoFile_is_explicitly_provided = &Message{code: 5074, category: CategoryError, key: "Option_incremental_is_only_valid_with_a_known_configuration_file_like_tsconfig_json_or_when_tsBuildI_5074", text: "Option '--incremental' is only valid with a known configuration file (like 'tsconfig.json') or when '--tsBuildInfoFile' is explicitly provided."} var X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2 = &Message{code: 5075, category: CategoryError, key: "_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_5075", text: "'{0}' is assignable to the constraint of type '{1}', but '{1}' could be instantiated with a different subtype of constraint '{2}'."} @@ -6558,8 +6558,8 @@ func keyToMessage(key Key) *Message { return Unknown_build_option_0 case "Build_option_0_requires_a_value_of_type_1_5073": return Build_option_0_requires_a_value_of_type_1 - case "Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBui_5074": - return Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified + case "Option_incremental_is_only_valid_with_a_known_configuration_file_like_tsconfig_json_or_when_tsBuildI_5074": + return Option_incremental_is_only_valid_with_a_known_configuration_file_like_tsconfig_json_or_when_tsBuildInfoFile_is_explicitly_provided case "_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_5075": return X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2 case "_0_and_1_operations_cannot_be_mixed_without_parentheses_5076": diff --git a/pkg/diagnostics/extraDiagnosticMessages.json b/pkg/diagnostics/extraDiagnosticMessages.json index e0f096b16..2c916ca5f 100644 --- a/pkg/diagnostics/extraDiagnosticMessages.json +++ b/pkg/diagnostics/extraDiagnosticMessages.json @@ -70,5 +70,9 @@ "tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error.": { "category": "Error", "code": 5112 + }, + "Option '--incremental' is only valid with a known configuration file (like 'tsconfig.json') or when '--tsBuildInfoFile' is explicitly provided.": { + "category": "Error", + "code": 5074 } } diff --git a/pkg/diagnostics/loc/cs-CZ.json.gz b/pkg/diagnostics/loc/cs-CZ.json.gz index df21c307f..93ec67c5f 100644 Binary files a/pkg/diagnostics/loc/cs-CZ.json.gz and b/pkg/diagnostics/loc/cs-CZ.json.gz differ diff --git a/pkg/diagnostics/loc/de-DE.json.gz b/pkg/diagnostics/loc/de-DE.json.gz index 5b398845e..ed29ca885 100644 Binary files a/pkg/diagnostics/loc/de-DE.json.gz and b/pkg/diagnostics/loc/de-DE.json.gz differ diff --git a/pkg/diagnostics/loc/es-ES.json.gz b/pkg/diagnostics/loc/es-ES.json.gz index d068162ce..15baec0e0 100644 Binary files a/pkg/diagnostics/loc/es-ES.json.gz and b/pkg/diagnostics/loc/es-ES.json.gz differ diff --git a/pkg/diagnostics/loc/fr-FR.json.gz b/pkg/diagnostics/loc/fr-FR.json.gz index ac9536dbe..d5533cba7 100644 Binary files a/pkg/diagnostics/loc/fr-FR.json.gz and b/pkg/diagnostics/loc/fr-FR.json.gz differ diff --git a/pkg/diagnostics/loc/it-IT.json.gz b/pkg/diagnostics/loc/it-IT.json.gz index 31328e9ee..634120a01 100644 Binary files a/pkg/diagnostics/loc/it-IT.json.gz and b/pkg/diagnostics/loc/it-IT.json.gz differ diff --git a/pkg/diagnostics/loc/ja-JP.json.gz b/pkg/diagnostics/loc/ja-JP.json.gz index 035a86ccc..5a6180541 100644 Binary files a/pkg/diagnostics/loc/ja-JP.json.gz and b/pkg/diagnostics/loc/ja-JP.json.gz differ diff --git a/pkg/diagnostics/loc/ko-KR.json.gz b/pkg/diagnostics/loc/ko-KR.json.gz index 05a48d314..37a06dc75 100644 Binary files a/pkg/diagnostics/loc/ko-KR.json.gz and b/pkg/diagnostics/loc/ko-KR.json.gz differ diff --git a/pkg/diagnostics/loc/pl-PL.json.gz b/pkg/diagnostics/loc/pl-PL.json.gz index 33a6f2812..2a3e091b8 100644 Binary files a/pkg/diagnostics/loc/pl-PL.json.gz and b/pkg/diagnostics/loc/pl-PL.json.gz differ diff --git a/pkg/diagnostics/loc/pt-BR.json.gz b/pkg/diagnostics/loc/pt-BR.json.gz index 2b8064be3..3ed87af15 100644 Binary files a/pkg/diagnostics/loc/pt-BR.json.gz and b/pkg/diagnostics/loc/pt-BR.json.gz differ diff --git a/pkg/diagnostics/loc/ru-RU.json.gz b/pkg/diagnostics/loc/ru-RU.json.gz index 0c31506b6..68648dc0e 100644 Binary files a/pkg/diagnostics/loc/ru-RU.json.gz and b/pkg/diagnostics/loc/ru-RU.json.gz differ diff --git a/pkg/diagnostics/loc/tr-TR.json.gz b/pkg/diagnostics/loc/tr-TR.json.gz index 4ef9c4594..246d34f0c 100644 Binary files a/pkg/diagnostics/loc/tr-TR.json.gz and b/pkg/diagnostics/loc/tr-TR.json.gz differ diff --git a/pkg/diagnostics/loc/zh-CN.json.gz b/pkg/diagnostics/loc/zh-CN.json.gz index 9498a59ae..49fc97fc5 100644 Binary files a/pkg/diagnostics/loc/zh-CN.json.gz and b/pkg/diagnostics/loc/zh-CN.json.gz differ diff --git a/pkg/diagnostics/loc/zh-TW.json.gz b/pkg/diagnostics/loc/zh-TW.json.gz index d95f48c53..1b5d5e6ce 100644 Binary files a/pkg/diagnostics/loc/zh-TW.json.gz and b/pkg/diagnostics/loc/zh-TW.json.gz differ diff --git a/pkg/execute/incremental/snapshottobuildinfo.go b/pkg/execute/incremental/snapshottobuildinfo.go index ab52192e3..729632cff 100644 --- a/pkg/execute/incremental/snapshottobuildinfo.go +++ b/pkg/execute/incremental/snapshottobuildinfo.go @@ -197,7 +197,7 @@ func (t *toBuildInfo) collectRootFiles() { } func (t *toBuildInfo) setFileInfoAndEmitSignatures() { - t.buildInfo.FileInfos = core.MapNonNil(t.program.GetSourceFiles(), func(file *ast.SourceFile) *BuildInfoFileInfo { + t.buildInfo.FileInfos = core.Map(t.program.GetSourceFiles(), func(file *ast.SourceFile) *BuildInfoFileInfo { info, _ := t.snapshot.fileInfos.Load(file.Path()) fileId := t.toFileId(file.Path()) // tryAddRoot(key, fileId); @@ -206,11 +206,6 @@ func (t *toBuildInfo) setFileInfoAndEmitSignatures() { panic(fmt.Sprintf("File name at index %d does not match expected relative path or libName: %s != %s", fileId-1, t.buildInfo.FileNames[fileId-1], t.relativeToBuildInfo(string(file.Path())))) } } - if int(fileId) != len(t.buildInfo.FileNames) { - // Duplicate - for now ignore - return nil - } - if t.snapshot.options.Composite.IsTrue() { if !ast.IsJsonSourceFile(file) && t.program.SourceFileMayBeEmitted(file, false) { if emitSignature, loaded := t.snapshot.emitSignatures.Load(file.Path()); !loaded { @@ -235,9 +230,6 @@ func (t *toBuildInfo) setFileInfoAndEmitSignatures() { } return newBuildInfoFileInfo(info) }) - if t.buildInfo.FileInfos == nil { - t.buildInfo.FileInfos = []*BuildInfoFileInfo{} - } } func (t *toBuildInfo) setRootOfIncrementalProgram() { diff --git a/pkg/execute/tsctests/tsc_test.go b/pkg/execute/tsctests/tsc_test.go index 60e051e30..02ed16cc2 100644 --- a/pkg/execute/tsctests/tsc_test.go +++ b/pkg/execute/tsctests/tsc_test.go @@ -981,6 +981,93 @@ func TestTscExtends(t *testing.T) { } } +func TestForceConsistentCasingInFileNames(t *testing.T) { + t.Parallel() + testCases := []*tscInput{ + { + subScenario: "with relative and non relative file resolutions", + files: FileMap{ + "/user/username/projects/myproject/src/struct.d.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/user/username/projects/myproject/node_modules/fp-ts/lib/struct.d.ts": `export function foo(): void`, + }, + cwd: "/user/username/projects/myproject", + commandLineArgs: []string{"/user/username/projects/myproject/src/struct.d.ts", "--forceConsistentCasingInFileNames", "--explainFiles"}, + ignoreCase: true, + }, + { + subScenario: "when file is included from multiple places with different casing", + files: FileMap{ + "/home/src/projects/project/src/struct.d.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/home/src/projects/project/src/anotherFile.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/home/src/projects/project/src/oneMore.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/home/src/projects/project/tsconfig.json": `{}`, + "/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts": `export function foo(): void`, + }, + cwd: "/home/src/projects/project", + commandLineArgs: []string{"--explainFiles"}, + ignoreCase: true, + }, + { + subScenario: "with type ref from file", + files: FileMap{ + "/user/username/projects/myproject/src/fileOne.d.ts": `declare class c { }`, + "/user/username/projects/myproject/src/file2.d.ts": stringtestutil.Dedent(` + /// + declare const y: c; + `), + "/user/username/projects/myproject/tsconfig.json": "{ }", + }, + cwd: "/user/username/projects/myproject", + commandLineArgs: []string{"-p", "/user/username/projects/myproject", "--explainFiles", "--traceResolution"}, + ignoreCase: true, + }, + { + subScenario: "with triple slash ref from file", + files: FileMap{ + "/home/src/workspaces/project/src/c.ts": `/// `, + "/home/src/workspaces/project/src/d.ts": `declare class c { }`, + "/home/src/workspaces/project/tsconfig.json": "{ }", + }, + ignoreCase: true, + }, + { + subScenario: "two files exist on disk that differs only in casing", + files: FileMap{ + "/home/src/workspaces/project/c.ts": `import {x} from "./D"`, + "/home/src/workspaces/project/D.ts": `export const x = 10;`, + "/home/src/workspaces/project/d.ts": `export const y = 20;`, + "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` + { + "files": ["c.ts", "d.ts"] + }`), + }, + }, + } + for _, test := range testCases { + test.run(t, "forceConsistentCasingInFileNames") + } +} + func TestTscIgnoreConfig(t *testing.T) { t.Parallel() filesWithoutConfig := func() FileMap { @@ -1745,6 +1832,54 @@ func TestTscIncremental(t *testing.T) { }, }, }, + { + subScenario: "Compile incremental with case insensitive file names", + commandLineArgs: []string{"-p", "."}, + files: FileMap{ + "/home/project/tsconfig.json": stringtestutil.Dedent(` + { + "compilerOptions": { + "incremental": true + }, + }`), + "/home/project/src/index.ts": stringtestutil.Dedent(` + import type { Foo1 } from 'lib1'; + import type { Foo2 } from 'lib2'; + export const foo1: Foo1 = { foo: "a" }; + export const foo2: Foo2 = { foo: "b" };`), + "/home/node_modules/lib1/index.d.ts": stringtestutil.Dedent(` + import type { Foo } from 'someLib'; + export type { Foo as Foo1 };`), + "/home/node_modules/lib1/package.json": stringtestutil.Dedent(` + { + "name": "lib1" + }`), + "/home/node_modules/lib2/index.d.ts": stringtestutil.Dedent(` + import type { Foo } from 'somelib'; + export type { Foo as Foo2 }; + export declare const foo2: Foo;`), + "/home/node_modules/lib2/package.json": stringtestutil.Dedent(` + { + "name": "lib2" + } + `), + "/home/node_modules/someLib/index.d.ts": stringtestutil.Dedent(` + import type { Str } from 'otherLib'; + export type Foo = { foo: Str; };`), + "/home/node_modules/someLib/package.json": stringtestutil.Dedent(` + { + "name": "somelib" + }`), + "/home/node_modules/otherLib/index.d.ts": stringtestutil.Dedent(` + export type Str = string;`), + "/home/node_modules/otherLib/package.json": stringtestutil.Dedent(` + { + "name": "otherlib" + }`), + }, + cwd: "/home/project", + ignoreCase: true, + }, } for _, test := range testCases { diff --git a/pkg/fourslash/_scripts/convertFourslash.mts b/pkg/fourslash/_scripts/convertFourslash.mts index 61b789df1..767c13b80 100644 --- a/pkg/fourslash/_scripts/convertFourslash.mts +++ b/pkg/fourslash/_scripts/convertFourslash.mts @@ -234,6 +234,9 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined { return [{ kind: "verifyBaselineDiagnostics" }]; case "navigateTo": return parseVerifyNavigateTo(callExpression.arguments); + case "outliningSpansInCurrentFile": + case "outliningHintSpansInCurrentFile": + return parseOutliningSpansArgs(callExpression.arguments); } } // `goTo....` @@ -2036,6 +2039,46 @@ function parseBaselineCallHierarchy(args: ts.NodeArray): Cmd { }; } +function parseOutliningSpansArgs(args: readonly ts.Expression[]): [VerifyOutliningSpansCmd] | undefined { + if (args.length === 0) { + console.error("Expected at least one argument in verify.outliningSpansInCurrentFile"); + return undefined; + } + + let spans: string = ""; + // Optional second argument for kind filter + let foldingRangeKind: string | undefined; + if (args.length > 1) { + const kindArg = getStringLiteralLike(args[1]); + if (!kindArg) { + console.error(`Expected string literal for outlining kind, got ${args[1].getText()}`); + return undefined; + } + switch (kindArg.text) { + case "comment": + foldingRangeKind = "lsproto.FoldingRangeKindComment"; + break; + case "region": + foldingRangeKind = "lsproto.FoldingRangeKindRegion"; + break; + case "imports": + foldingRangeKind = "lsproto.FoldingRangeKindImports"; + break; + case "code": + break; + default: + console.error(`Unknown folding range kind: ${kindArg.text}`); + return undefined; + } + } + + return [{ + kind: "verifyOutliningSpans", + spans, + foldingRangeKind, + }]; +} + function parseKind(expr: ts.Expression): string | undefined { if (!ts.isStringLiteral(expr)) { console.error(`Expected string literal for kind, got ${expr.getText()}`); @@ -2513,6 +2556,12 @@ interface VerifyNoSignatureHelpForTriggerReasonCmd { markers: string[]; } +interface VerifyOutliningSpansCmd { + kind: "verifyOutliningSpans"; + spans: string; + foldingRangeKind?: string; +} + type Cmd = | VerifyCompletionsCmd | VerifyApplyCodeActionFromCompletionCmd @@ -2536,7 +2585,15 @@ type Cmd = | VerifyBaselineInlayHintsCmd | VerifyImportFixAtPositionCmd | VerifyDiagnosticsCmd - | VerifyBaselineDiagnosticsCmd; + | VerifyBaselineDiagnosticsCmd + | VerifyOutliningSpansCmd; + +function generateVerifyOutliningSpans({ foldingRangeKind }: VerifyOutliningSpansCmd): string { + if (foldingRangeKind) { + return `f.VerifyOutliningSpans(t, ${foldingRangeKind})`; + } + return `f.VerifyOutliningSpans(t)`; +} function generateVerifyCompletions({ marker, args, isNewIdentifierLocation, andApplyCodeActionArgs }: VerifyCompletionsCmd): string { let expectedList: string; @@ -2830,6 +2887,8 @@ function generateCmd(cmd: Cmd): string { return generateSignatureHelpPresent(cmd); case "verifyNoSignatureHelpForTriggerReason": return generateNoSignatureHelpForTriggerReason(cmd); + case "verifyOutliningSpans": + return generateVerifyOutliningSpans(cmd); default: let neverCommand: never = cmd; throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`); diff --git a/pkg/fourslash/_scripts/manualTests.txt b/pkg/fourslash/_scripts/manualTests.txt index 970916f31..c86bec5d2 100644 --- a/pkg/fourslash/_scripts/manualTests.txt +++ b/pkg/fourslash/_scripts/manualTests.txt @@ -15,3 +15,7 @@ renameForDefaultExport01 tsxCompletion12 jsDocFunctionSignatures2 jsDocFunctionSignatures12 +outliningHintSpansForFunction +getOutliningSpans +outliningForNonCompleteInterfaceDeclaration +incrementalParsingWithJsDoc \ No newline at end of file diff --git a/pkg/fourslash/fourslash.go b/pkg/fourslash/fourslash.go index c891f9ee8..f509c4793 100644 --- a/pkg/fourslash/fourslash.go +++ b/pkg/fourslash/fourslash.go @@ -1521,6 +1521,55 @@ func (f *FourslashTest) VerifyBaselineWorkspaceSymbol(t *testing.T, query string )) } +func (f *FourslashTest) VerifyOutliningSpans(t *testing.T, foldingRangeKind ...lsproto.FoldingRangeKind) { + params := &lsproto.FoldingRangeParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + }, + } + result := sendRequest(t, f, lsproto.TextDocumentFoldingRangeInfo, params) + if result.FoldingRanges == nil { + t.Fatalf("Nil response received for folding range request") + } + + // Extract actual folding ranges from the result and filter by kind if specified + var actualRanges []*lsproto.FoldingRange + actualRanges = *result.FoldingRanges + if len(foldingRangeKind) > 0 { + targetKind := foldingRangeKind[0] + var filtered []*lsproto.FoldingRange + for _, r := range actualRanges { + if r.Kind != nil && *r.Kind == targetKind { + filtered = append(filtered, r) + } + } + actualRanges = filtered + } + + if len(actualRanges) != len(f.Ranges()) { + t.Fatalf("verifyOutliningSpans failed - expected total spans to be %d, but was %d", + len(f.Ranges()), len(actualRanges)) + } + + slices.SortFunc(f.Ranges(), func(a, b *RangeMarker) int { + return lsproto.ComparePositions(a.LSPos(), b.LSPos()) + }) + + for i, expectedRange := range f.Ranges() { + actualRange := actualRanges[i] + startPos := lsproto.Position{Line: actualRange.StartLine, Character: *actualRange.StartCharacter} + endPos := lsproto.Position{Line: actualRange.EndLine, Character: *actualRange.EndCharacter} + + if lsproto.ComparePositions(startPos, expectedRange.LSRange.Start) != 0 || + lsproto.ComparePositions(endPos, expectedRange.LSRange.End) != 0 { + t.Fatalf("verifyOutliningSpans failed - span %d has invalid positions:\n actual: start (%d,%d), end (%d,%d)\n expected: start (%d,%d), end (%d,%d)", + i+1, + actualRange.StartLine, *actualRange.StartCharacter, actualRange.EndLine, *actualRange.EndCharacter, + expectedRange.LSRange.Start.Line, expectedRange.LSRange.Start.Character, expectedRange.LSRange.End.Line, expectedRange.LSRange.End.Character) + } + } +} + func (f *FourslashTest) VerifyBaselineHover(t *testing.T) { markersAndItems := core.MapFiltered(f.Markers(), func(marker *Marker) (markerAndItem[*lsproto.Hover], bool) { if marker.Name == nil { diff --git a/pkg/fourslash/tests/gen/correuptedTryExpressionsDontCrashGettingOutlineSpans_test.go b/pkg/fourslash/tests/gen/correuptedTryExpressionsDontCrashGettingOutlineSpans_test.go new file mode 100644 index 000000000..a3f53a80e --- /dev/null +++ b/pkg/fourslash/tests/gen/correuptedTryExpressionsDontCrashGettingOutlineSpans_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestCorreuptedTryExpressionsDontCrashGettingOutlineSpans(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `try[| { + var x = [ + {% try[||] %}|][|{% except %}|] + ] +} catch (e)[| { + +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningForArrayDestructuring_test.go b/pkg/fourslash/tests/gen/getOutliningForArrayDestructuring_test.go new file mode 100644 index 000000000..40165b858 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningForArrayDestructuring_test.go @@ -0,0 +1,56 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningForArrayDestructuring(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `const[| [ + a, + b, + c +]|] =[| [ + 1, + 2, + 3 +]|]; +const[| [ + [|[ + [|[ + [|[ + a, + b, + c + ]|] + ]|] + ]|], + [|[ + a1, + b1, + c1 + ]|] +]|] =[| [ + [|[ + [|[ + [|[ + 1, + 2, + 3 + ]|] + ]|] + ]|], + [|[ + 1, + 2, + 3 + ]|] +]|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningForBlockComments_test.go b/pkg/fourslash/tests/gen/getOutliningForBlockComments_test.go new file mode 100644 index 000000000..26e6b57e8 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningForBlockComments_test.go @@ -0,0 +1,347 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningForBlockComments(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|/* + Block comment at the beginning of the file before module: + line one of the comment + line two of the comment + line three + line four + line five +*/|] +module Sayings[| { + [|/* + Comment before class: + line one of the comment + line two of the comment + line three + line four + line five + */|] + export class Greeter[| { + [|/* + Comment before a string identifier + line two of the comment + */|] + greeting: string; + [|/* + constructor + parameter message as a string + */|] + + [|/* + Multiple comments should be collapsed individually + */|] + constructor(message: string /* do not collapse this */)[| { + this.greeting = message; + }|] + [|/* + method of a class + */|] + greet()[| { + return "Hello, " + this.greeting; + }|] + }|] +}|] + +[|/* + Block comment for interface. The ending can be on the same line as the declaration. +*/|]interface IFoo[| { + [|/* + Multiple block comments + */|] + + [|/* + should be collapsed + */|] + + [|/* + individually + */|] + + [|/* + this comment has trailing space before /* and after *-/ signs + */|] + + [|/** + * + * + * + */|] + + [|/* + */|] + + [|/* + */|] + // single line comments in the middle should not have an effect + [|/* + */|] + + [|/* + */|] + + [|/* + this block comment ends + on the same line */|] [|/* where the following comment starts + should be collapsed separately + */|] + + getDist(): number; +}|] + +var x =[|{ + a:1, + b: 2, + [|/* + Over a function in an object literal + */|] + get foo()[| { + return 1; + }|] +}|] + +// Over a function expression assigned to a variable + [|/** + * Return a sum + * @param {Number} y + * @param {Number} z + * @returns {Number} the sum of y and z + */|] + const sum2 = (y, z) =>[| { + return y + z; + }|]; + +// Over a variable +[|/** + * foo + */|] +const foo = null; + +function Foo()[| { + [|/** + * Description + * + * @param {string} param + * @returns + */|] + this.method = function (param)[| { + }|] + + [|/** + * Description + * + * @param {string} param + * @returns + */|] + function method(param)[| { + }|] +}|] + +function fn1()[| { + [|/** + * comment + */|] +}|] +function fn2()[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +function fn3()[| { + const x = 1; + + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +function fn4()[| { + [|/** + * comment + */|] + const x = 1; + + [|/** + * comment + */|] +}|] +function fn5()[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] + return 1; +}|] +function fn6()[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] + const x = 1; +}|] + +[|/* +comment +*/|] + +f6(); + +class C1[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +class C2[| { + private prop = 1; + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +class C3[| { + [|/** + * comment + */|] + + private prop = 1; + [|/** + * comment + */|] +}|] +class C4[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] + private prop = 1; +}|] + +[|/* +comment +*/|] +new C4(); + +module M1[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +module M2[| { + export const a = 1; + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +module M3[| { + [|/** + * comment + */|] + export const a = 1; + + [|/** + * comment + */|] +}|] +module M4[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] + export const a = 1; +}|] +interface I1[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +interface I2[| { + x: number; + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|] +interface I3[| { + [|/** + * comment + */|] + x: number; + + [|/** + * comment + */|] +}|] +interface I4[| { + [|/** + * comment + */|] + + [|/** + * comment + */|] + x: number; +}|] +[|{ + [|/** + * comment + */|] + + [|/** + * comment + */|] +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningForObjectDestructuring_test.go b/pkg/fourslash/tests/gen/getOutliningForObjectDestructuring_test.go new file mode 100644 index 000000000..fdc8b2962 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningForObjectDestructuring_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningForObjectDestructuring(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `const[| { + a, + b, + c +}|] =[| { + a: 1, + b: 2, + c: 3 +}|] +const[| { + a:[| { + a_1, + a_2, + a_3:[| { + a_3_1, + a_3_2, + a_3_3, + }|], + }|], + b, + c +}|] =[| { + a:[| { + a_1: 1, + a_2: 2, + a_3:[| { + a_3_1: 1, + a_3_2: 1, + a_3_3: 1 + }|], + }|], + b: 2, + c: 3 +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningForObjectsInArray_test.go b/pkg/fourslash/tests/gen/getOutliningForObjectsInArray_test.go new file mode 100644 index 000000000..352c97a4c --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningForObjectsInArray_test.go @@ -0,0 +1,64 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningForObjectsInArray(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `const x =[| [ + [|{ a: 0 }|], + [|{ b: 1 }|], + [|{ c: 2 }|] +]|]; + +const y =[| [ + [|{ + a: 0 + }|], + [|{ + b: 1 + }|], + [|{ + c: 2 + }|] +]|]; + +const w =[| [ + [|[ 0 ]|], + [|[ 1 ]|], + [|[ 2 ]|] +]|]; + +const z =[| [ + [|[ + 0 + ]|], + [|[ + 1 + ]|], + [|[ + 2 + ]|] +]|]; + +const z =[| [ + [|[ + [|{ hello: 0 }|] + ]|], + [|[ + [|{ hello: 3 }|] + ]|], + [|[ + [|{ hello: 5 }|], + [|{ hello: 7 }|] + ]|] +]|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningForSingleLineComments_test.go b/pkg/fourslash/tests/gen/getOutliningForSingleLineComments_test.go new file mode 100644 index 000000000..1e9c2c6c2 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningForSingleLineComments_test.go @@ -0,0 +1,100 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningForSingleLineComments(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|// Single line comments at the start of the file +// line 2 +// line 3 +// line 4|] +module Sayings[| { + + [|/* + */|] + [|// A sequence of + // single line|] + [|/* + and block + */|] + [|// comments + //|] + export class Sample[| { + }|] +}|] + +interface IFoo[| { + [|// all consecutive single line comments should be in one block regardless of their number or empty lines/spaces inbetween + + // comment 2 + // comment 3 + + //comment 4 + /// comment 5 + ///// comment 6 + + //comment 7 + ///comment 8 + // comment 9 + // //comment 10 + + + + + + + + + + + + + + + + + + + + + // // //comment 11 + // comment 12 + // comment 13 + // comment 14 + // comment 15 + + // comment 16 + // comment 17 + // comment 18 + // comment 19 + // comment 20 + // comment 21|] + + getDist(): number; // One single line comment should not be collapsed +}|] + +// One single line comment should not be collapsed +class WithOneSingleLineComment[| { +}|] + +function Foo()[| { + [|// comment 1 + // comment 2|] + this.method = function (param)[| { + }|] + + [|// comment 1 + // comment 2|] + function method(param)[| { + }|] +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningForTupleType_test.go b/pkg/fourslash/tests/gen/getOutliningForTupleType_test.go new file mode 100644 index 000000000..fdce20294 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningForTupleType_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningForTupleType(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `type A =[| [ + number, + number, + number +]|] + +type B =[| [ + [|[ + [|[ + number, + number, + number + ]|] + ]|] +]|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningForTypeLiteral_test.go b/pkg/fourslash/tests/gen/getOutliningForTypeLiteral_test.go new file mode 100644 index 000000000..a211cae5a --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningForTypeLiteral_test.go @@ -0,0 +1,36 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningForTypeLiteral(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `type A =[| { + a: number; +}|] + +type B =[| { + a:[| { + a1:[| { + a2:[| { + x: number; + y: number; + }|] + }|] + }|], + b:[| { + x: number; + }|], + c:[| { + x: number; + }|] +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansDepthChainedCalls_test.go b/pkg/fourslash/tests/gen/getOutliningSpansDepthChainedCalls_test.go new file mode 100644 index 000000000..803e5a077 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansDepthChainedCalls_test.go @@ -0,0 +1,126 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansDepthChainedCalls(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `declare var router: any; +router + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|] + .get[|("/", async(ctx) =>[|{ + ctx.body = "base"; + }|])|] + .post[|("/a", async(ctx) =>[|{ + //a + }|])|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansDepthElseIf_test.go b/pkg/fourslash/tests/gen/getOutliningSpansDepthElseIf_test.go new file mode 100644 index 000000000..a32a75bcb --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansDepthElseIf_test.go @@ -0,0 +1,99 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansDepthElseIf(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else if (1)[| { + 1; +}|] else[| { + 1; +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansForComments_test.go b/pkg/fourslash/tests/gen/getOutliningSpansForComments_test.go new file mode 100644 index 000000000..2bf9e6ee1 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansForComments_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansForComments(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|/* + Block comment at the beginning of the file before module: + line one of the comment + line two of the comment + line three + line four + line five +*/|] +declare module "m"; +[|// Single line comments at the start of the file +// line 2 +// line 3 +// line 4|] +declare module "n";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.MarkTestAsStradaServer() + f.VerifyOutliningSpans(t, lsproto.FoldingRangeKindComment) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansForImports_test.go b/pkg/fourslash/tests/gen/getOutliningSpansForImports_test.go new file mode 100644 index 000000000..4dcca5393 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansForImports_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansForImports(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import * as ns from "mod"; + +import d from "mod"; +import { a, b, c } from "mod"; + +import r = require("mod");|] + +// statement +var x = 0; + +// another set of imports +[|import * as ns from "mod"; +import d from "mod"; +import { a, b, c } from "mod"; +import r = require("mod");|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t, lsproto.FoldingRangeKindImports) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansForRegionsNoSingleLineFolds_test.go b/pkg/fourslash/tests/gen/getOutliningSpansForRegionsNoSingleLineFolds_test.go new file mode 100644 index 000000000..916719d99 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansForRegionsNoSingleLineFolds_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansForRegionsNoSingleLineFolds(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|//#region +function foo()[| { + +}|] +[|//these +//should|] +//#endregion not you|] +[|// be +// together|] + +[|//#region bla bla bla + +function bar()[| { }|] + +//#endregion|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.MarkTestAsStradaServer() + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansForRegions_test.go b/pkg/fourslash/tests/gen/getOutliningSpansForRegions_test.go new file mode 100644 index 000000000..483161daa --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansForRegions_test.go @@ -0,0 +1,65 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansForRegions(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// region without label +[|// #region + +// #endregion|] + +// region without label with trailing spaces +[|// #region + +// #endregion|] + +// region with label +[|// #region label1 + +// #endregion|] + +// region with extra whitespace in all valid locations + [|// #region label2 label3 + + // #endregion|] + +// No space before directive +[|//#region label4 + +//#endregion|] + +// Nested regions +[|// #region outer + +[|// #region inner + +// #endregion inner|] + +// #endregion outer|] + +// region delimiters not valid when there is preceding text on line + test // #region invalid1 + +test // #endregion + +// region delimiters not valid when in multiline comment +/* +// #region invalid2 +*/ + +/* +// #endregion +*/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.MarkTestAsStradaServer() + f.VerifyOutliningSpans(t, lsproto.FoldingRangeKindRegion) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansForTemplateLiteral_test.go b/pkg/fourslash/tests/gen/getOutliningSpansForTemplateLiteral_test.go new file mode 100644 index 000000000..dbeb15f8f --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansForTemplateLiteral_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansForTemplateLiteral(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `declare function tag(...args: any[]): void +const a = [|` + "`" + `signal line` + "`" + `|] +const b = [|` + "`" + `multi +line` + "`" + `|] +const c = tag[|` + "`" + `signal line` + "`" + `|] +const d = tag[|` + "`" + `multi +line` + "`" + `|] +const e = [|` + "`" + `signal ${1} line` + "`" + `|] +const f = [|` + "`" + `multi +${1} +line` + "`" + `|] +const g = tag[|` + "`" + `signal ${1} line` + "`" + `|] +const h = tag[|` + "`" + `multi +${1} +line` + "`" + `|] +const i = ` + "`" + `` + "`" + `` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansForUnbalancedEndRegion_test.go b/pkg/fourslash/tests/gen/getOutliningSpansForUnbalancedEndRegion_test.go new file mode 100644 index 000000000..3af1c195e --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansForUnbalancedEndRegion_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansForUnbalancedEndRegion(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// bottom-heavy region balance +[|// #region matched + +// #endregion matched|] + +// #endregion unmatched` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t, lsproto.FoldingRangeKindRegion) +} diff --git a/pkg/fourslash/tests/gen/getOutliningSpansForUnbalancedRegion_test.go b/pkg/fourslash/tests/gen/getOutliningSpansForUnbalancedRegion_test.go new file mode 100644 index 000000000..8947696b3 --- /dev/null +++ b/pkg/fourslash/tests/gen/getOutliningSpansForUnbalancedRegion_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpansForUnbalancedRegion(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// top-heavy region balance +// #region unmatched + +[|// #region matched + +// #endregion matched|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t, lsproto.FoldingRangeKindRegion) +} diff --git a/pkg/fourslash/tests/gen/outlineSpansBlockCommentsWithoutStatements_test.go b/pkg/fourslash/tests/gen/outlineSpansBlockCommentsWithoutStatements_test.go new file mode 100644 index 000000000..fb5b1bd79 --- /dev/null +++ b/pkg/fourslash/tests/gen/outlineSpansBlockCommentsWithoutStatements_test.go @@ -0,0 +1,19 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutlineSpansBlockCommentsWithoutStatements(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|/* +/ * Some text + */|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outlineSpansTrailingBlockCommentsAfterStatements_test.go b/pkg/fourslash/tests/gen/outlineSpansTrailingBlockCommentsAfterStatements_test.go new file mode 100644 index 000000000..b5672e0a1 --- /dev/null +++ b/pkg/fourslash/tests/gen/outlineSpansTrailingBlockCommentsAfterStatements_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutlineSpansTrailingBlockCommentsAfterStatements(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `console.log(0); +[|/* +/ * Some text + */|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outliningSpansForArguments_test.go b/pkg/fourslash/tests/gen/outliningSpansForArguments_test.go new file mode 100644 index 000000000..9f46d8b9a --- /dev/null +++ b/pkg/fourslash/tests/gen/outliningSpansForArguments_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningSpansForArguments(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `console.log(123, 456)l; +console.log( +); +console.log[|( + 123, 456 +)|]; +console.log[|( + 123, + 456 +)|]; +() =>[| console.log[|( + 123, + 456 +)|]|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outliningSpansForArrowFunctionBody_test.go b/pkg/fourslash/tests/gen/outliningSpansForArrowFunctionBody_test.go new file mode 100644 index 000000000..8544beb1a --- /dev/null +++ b/pkg/fourslash/tests/gen/outliningSpansForArrowFunctionBody_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningSpansForArrowFunctionBody(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `() => 42; +() => ( 42 ); +() =>[| { + 42 +}|]; +() => [|( + 42 +)|]; +() =>[| "foo" + + "bar" + + "baz"|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outliningSpansForFunction_test.go b/pkg/fourslash/tests/gen/outliningSpansForFunction_test.go new file mode 100644 index 000000000..354d35579 --- /dev/null +++ b/pkg/fourslash/tests/gen/outliningSpansForFunction_test.go @@ -0,0 +1,96 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningSpansForFunction(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|( + a: number, + b: number +) => { + return a + b; +}|]; + +(a: number, b: number) =>[| { + return a + b; +}|] + +const f1 = function[| ( + a: number + b: number +) { + return a + b; +}|] + +const f2 = function (a: number, b: number)[| { + return a + b; +}|] + +function f3[| ( + a: number + b: number +) { + return a + b; +}|] + +function f4(a: number, b: number)[| { + return a + b; +}|] + +class Foo[| { + constructor[|( + a: number, + b: number + ) { + this.a = a; + this.b = b; + }|] + + m1[|( + a: number, + b: number + ) { + return a + b; + }|] + + m1(a: number, b: number)[| { + return a + b; + }|] +}|] + +declare function foo(props: any): void; +foo[|( + a =>[| { + + }|] +)|] + +foo[|( + (a) =>[| { + + }|] +)|] + +foo[|( + (a, b, c) =>[| { + + }|] +)|] + +foo[|([| + (a, + b, + c) => { + + }|] +)|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outliningSpansForImportAndExportAttributes_test.go b/pkg/fourslash/tests/gen/outliningSpansForImportAndExportAttributes_test.go new file mode 100644 index 000000000..a12d2850e --- /dev/null +++ b/pkg/fourslash/tests/gen/outliningSpansForImportAndExportAttributes_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningSpansForImportAndExportAttributes(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import { a1, a2 } from "a"; +; +import { +} from "a"; +; +import [|{ + b1, + b2, +}|] from "b"; +; +import j1 from "./j" with { type: "json" }; +; +import j2 from "./j" with { +}; +; +import j3 from "./j" with [|{ + type: "json" +}|]; +; +[|import { a5, a6 } from "a"; +import [|{ + a7, + a8, +}|] from "a";|] +export { a1, a2 }; +; +export { a3, a4 } from "a"; +; +export { +}; +; +export [|{ + b1, + b2, +}|]; +; +export { +} from "b"; +; +export [|{ + b3, + b4, +}|] from "b"; +;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outliningSpansForImportsAndExports_test.go b/pkg/fourslash/tests/gen/outliningSpansForImportsAndExports_test.go new file mode 100644 index 000000000..e42f1bfbd --- /dev/null +++ b/pkg/fourslash/tests/gen/outliningSpansForImportsAndExports_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningSpansForImportsAndExports(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import { a1, a2 } from "a"; +; +import { +} from "a"; +; +import [|{ + b1, + b2, +}|] from "b"; +; +import j1 from "./j" assert { type: "json" }; +; +import j2 from "./j" assert { +}; +; +import j3 from "./j" assert [|{ + type: "json" +}|]; +; +[|import { a5, a6 } from "a"; +import [|{ + a7, + a8, +}|] from "a";|] +export { a1, a2 }; +; +export { a3, a4 } from "a"; +; +export { +}; +; +export [|{ + b1, + b2, +}|]; +; +export { +} from "b"; +; +export [|{ + b3, + b4, +}|] from "b"; +;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outliningSpansForParenthesizedExpression_test.go b/pkg/fourslash/tests/gen/outliningSpansForParenthesizedExpression_test.go new file mode 100644 index 000000000..15ea1da39 --- /dev/null +++ b/pkg/fourslash/tests/gen/outliningSpansForParenthesizedExpression_test.go @@ -0,0 +1,45 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningSpansForParenthesizedExpression(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `const a = [|( + true + ? true + : false + ? true + : false +)|]; + +const b = ( 1 ); + +const c = [|( + 1 +)|]; + +( 1 ); + +[|( + [|( + [|( + 1 + )|] + )|] +)|]; + +[|( + [|( + ( 1 ) + )|] +)|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/gen/outliningSpansSwitchCases_test.go b/pkg/fourslash/tests/gen/outliningSpansSwitchCases_test.go new file mode 100644 index 000000000..9fa4e648b --- /dev/null +++ b/pkg/fourslash/tests/gen/outliningSpansSwitchCases_test.go @@ -0,0 +1,49 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningSpansSwitchCases(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `switch (undefined)[| { + case 0:[| + console.log(1) + console.log(2) + break; + console.log(3);|] + case 1:[| + break;|] + case 2:[| + break; + console.log(3);|] + case 3:[| + console.log(4);|] + + case 4: + case 5: + case 6:[| + + + console.log(5);|] + + case 7:[| console.log(6);|] + + case 8:[| [|{ + console.log(8); + break; + }|] + console.log(8);|] + + default:[| + console.log(7); + console.log(8);|] +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/manual/getOutliningSpans_test.go b/pkg/fourslash/tests/manual/getOutliningSpans_test.go new file mode 100644 index 000000000..01315bda3 --- /dev/null +++ b/pkg/fourslash/tests/manual/getOutliningSpans_test.go @@ -0,0 +1,140 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestGetOutliningSpans(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// interface +interface IFoo[| { + getDist(): number; +}|] + +// class members +class Foo[| { + constructor()[| { + }|] + + public foo(): number[| { + return 0; + }|] + + public get X()[| { + return 1; + }|] + + public set X(v: number)[| { + }|] + + public member = function f()[| { + + }|] +}|] +// class expressions + [|(new class[| { + bla()[| { + + }|] + }|])|] +switch(1)[| { + case 1:[| break;|] +}|] + +var array =[| [ + 1, + 2 +]|] + +// modules +module m1[| { + module m2[| { }|] + module m3[| { + function foo()[| { + + }|] + + interface IFoo2[| { + + }|] + + class foo2 implements IFoo2[| { + + }|] + }|] +}|] + +// function declaration +function foo(): number[| { + return 0; +}|] + +// function expressions +[|(function f()[| { + +}|])|] + +// trivia handeling +class ClassFooWithTrivia[| /* some comments */ + /* more trivia */ { + + + [|/*some trailing trivia */|] +}|] /* even more */ + +// object literals +var x =[|{ + a:1, + b:2, + get foo()[| { + return 1; + }|] +}|] +//outline with deep nesting +var nest =[| [[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[[|[ + [|[ + [ + [ + [ + [ + 1,2,3 + ] + ] + ] + ] + ]|] +]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]]|]; + +//outline after a deeply nested node +class AfterNestedNodes[| { +}|] +// function arguments +function f(x: number[], y: number[])[| { + return 3; +}|] +f[|( +// single line array literal span won't render in VS + [|[0]|], + [|[ + 1, + 2 + ]|] +)|]; + +class C[| { + foo: T; +}|] + +class D extends C[| { + constructor(x)[| { + super(x); + }|] +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/manual/incrementalParsingWithJsDoc_test.go b/pkg/fourslash/tests/manual/incrementalParsingWithJsDoc_test.go new file mode 100644 index 000000000..4010160b9 --- /dev/null +++ b/pkg/fourslash/tests/manual/incrementalParsingWithJsDoc_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestIncrementalParsingWithJsDoc(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import a from 'a/aaaaaaa/aaaaaaa/aaaaaa/aaaaaaa'; +/**/import b from 'b'; +import c from 'c';|] +[|/** @internal */|] +export class LanguageIdentifier[| { }|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) + f.GoToMarker(t, "") + f.Backspace(t, 1) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/manual/outliningForNonCompleteInterfaceDeclaration_test.go b/pkg/fourslash/tests/manual/outliningForNonCompleteInterfaceDeclaration_test.go new file mode 100644 index 000000000..b675c3cfc --- /dev/null +++ b/pkg/fourslash/tests/manual/outliningForNonCompleteInterfaceDeclaration_test.go @@ -0,0 +1,17 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningForNonCompleteInterfaceDeclaration(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `interface I` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/fourslash/tests/manual/outliningHintSpansForFunction_test.go b/pkg/fourslash/tests/manual/outliningHintSpansForFunction_test.go new file mode 100644 index 000000000..0b14ecb22 --- /dev/null +++ b/pkg/fourslash/tests/manual/outliningHintSpansForFunction_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestOutliningHintSpansForFunction(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `namespace NS[| { + function f(x: number, y: number)[| { + return x + y; + }|] + + function g[|( + x: number, + y: number, + ): number { + return x + y; + }|] +}|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyOutliningSpans(t) +} diff --git a/pkg/ls/completions.go b/pkg/ls/completions.go index d00fe3823..4dd05a71d 100644 --- a/pkg/ls/completions.go +++ b/pkg/ls/completions.go @@ -682,7 +682,7 @@ func (l *LanguageService) getCompletionData( // Note for `
` we don't want to treat this as a jsx inializer, instead it's the attribute name. if parent != previousToken.Parent && parent.Initializer() == nil && - findChildOfKind(parent, ast.KindEqualsToken, file) != nil { + astnav.FindChildOfKind(parent, ast.KindEqualsToken, file) != nil { jsxInitializer.initializer = previousToken } } @@ -2080,9 +2080,9 @@ func (l *LanguageService) createCompletionItem( insertText = "?." + insertText } - dot := findChildOfKind(data.propertyAccessToConvert, ast.KindDotToken, file) + dot := astnav.FindChildOfKind(data.propertyAccessToConvert, ast.KindDotToken, file) if dot == nil { - dot = findChildOfKind(data.propertyAccessToConvert, ast.KindQuestionDotToken, file) + dot = astnav.FindChildOfKind(data.propertyAccessToConvert, ast.KindQuestionDotToken, file) } if dot == nil { @@ -4072,7 +4072,7 @@ func tryGetObjectTypeDeclarationCompletionContainer( stmtList := location.Parent.StatementList() if stmtList != nil && len(stmtList.Nodes) > 0 && ast.IsObjectTypeDeclaration(stmtList.Nodes[len(stmtList.Nodes)-1]) { cls := stmtList.Nodes[len(stmtList.Nodes)-1] - if findChildOfKind(cls, ast.KindCloseBraceToken, file) == nil { + if astnav.FindChildOfKind(cls, ast.KindCloseBraceToken, file) == nil { return cls } } @@ -4424,7 +4424,7 @@ func (l *LanguageService) getJsxClosingTagCompletion( // var x = // var y = // the completion list at "1" and "2" will contain "MainComponent.Child" with a replacement span of closing tag name - hasClosingAngleBracket := findChildOfKind(jsxClosingElement, ast.KindGreaterThanToken, file) != nil + hasClosingAngleBracket := astnav.FindChildOfKind(jsxClosingElement, ast.KindGreaterThanToken, file) != nil tagName := jsxClosingElement.Parent.AsJsxElement().OpeningElement.TagName() closingTag := scanner.GetTextOfNode(tagName) fullClosingTag := closingTag + core.IfElse(hasClosingAngleBracket, "", ">") diff --git a/pkg/ls/documenthighlights.go b/pkg/ls/documenthighlights.go index 1d5567aa7..b45dd1357 100644 --- a/pkg/ls/documenthighlights.go +++ b/pkg/ls/documenthighlights.go @@ -276,7 +276,7 @@ func getReturnOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Nod body := funcNode.Body() if body != nil { ast.ForEachReturnStatement(body, func(ret *ast.Node) bool { - keyword := findChildOfKind(ret, ast.KindReturnKeyword, sourceFile) + keyword := astnav.FindChildOfKind(ret, ast.KindReturnKeyword, sourceFile) if keyword != nil { keywords = append(keywords, keyword) } @@ -286,7 +286,7 @@ func getReturnOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Nod // Get all throw statements not in a try block throwStatements := aggregateOwnedThrowStatements(body, sourceFile) for _, throw := range throwStatements { - keyword := findChildOfKind(throw, ast.KindThrowKeyword, sourceFile) + keyword := astnav.FindChildOfKind(throw, ast.KindThrowKeyword, sourceFile) if keyword != nil { keywords = append(keywords, keyword) } @@ -348,7 +348,7 @@ func getThrowOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node // Aggregate all throw statements "owned" by this owner. throwStatements := aggregateOwnedThrowStatements(owner, sourceFile) for _, throw := range throwStatements { - keyword := findChildOfKind(throw, ast.KindThrowKeyword, sourceFile) + keyword := astnav.FindChildOfKind(throw, ast.KindThrowKeyword, sourceFile) if keyword != nil { keywords = append(keywords, keyword) } @@ -358,7 +358,7 @@ func getThrowOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node // ability to "jump out" of the function, and include occurrences for both if ast.IsFunctionBlock(owner) { ast.ForEachReturnStatement(owner, func(ret *ast.Node) bool { - keyword := findChildOfKind(ret, ast.KindReturnKeyword, sourceFile) + keyword := astnav.FindChildOfKind(ret, ast.KindReturnKeyword, sourceFile) if keyword != nil { keywords = append(keywords, keyword) } @@ -412,7 +412,7 @@ func getTryCatchFinallyOccurrences(node *ast.Node, sourceFile *ast.SourceFile) [ } if tryStatement.FinallyBlock != nil { - finallyKeyword := findChildOfKind(node, ast.KindFinallyKeyword, sourceFile) + finallyKeyword := astnav.FindChildOfKind(node, ast.KindFinallyKeyword, sourceFile) if finallyKeyword.Kind == ast.KindFinallyKeyword { keywords = append(keywords, finallyKeyword) } diff --git a/pkg/ls/findallreferences.go b/pkg/ls/findallreferences.go index 5055cbda4..0bc52f9d0 100644 --- a/pkg/ls/findallreferences.go +++ b/pkg/ls/findallreferences.go @@ -1357,7 +1357,7 @@ func (l *LanguageService) getReferencedSymbolsForModule(ctx context.Context, pro node = decl.AsBinaryExpression().Left.Expression() } else if ast.IsExportAssignment(decl) { // Find the export keyword - node = findChildOfKind(decl, ast.KindExportKeyword, sourceFile) + node = astnav.FindChildOfKind(decl, ast.KindExportKeyword, sourceFile) debug.Assert(node != nil, "Expected to find export keyword") } else { node = ast.GetNameOfDeclaration(decl) @@ -1645,7 +1645,7 @@ func findOwnConstructorReferences(classSymbol *ast.Symbol, sourceFile *ast.Sourc if constructorSymbol != nil && len(constructorSymbol.Declarations) > 0 { for _, decl := range constructorSymbol.Declarations { if decl.Kind == ast.KindConstructor { - if ctrKeyword := findChildOfKind(decl, ast.KindConstructorKeyword, sourceFile); ctrKeyword != nil { + if ctrKeyword := astnav.FindChildOfKind(decl, ast.KindConstructorKeyword, sourceFile); ctrKeyword != nil { addNode(ctrKeyword) } } diff --git a/pkg/ls/folding.go b/pkg/ls/folding.go new file mode 100644 index 000000000..1ea81f908 --- /dev/null +++ b/pkg/ls/folding.go @@ -0,0 +1,533 @@ +package ls + +import ( + "cmp" + "context" + "slices" + "strings" + "unicode" + + "github.com/buke/typescript-go-internal/pkg/ast" + "github.com/buke/typescript-go-internal/pkg/astnav" + "github.com/buke/typescript-go-internal/pkg/debug" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/printer" + "github.com/buke/typescript-go-internal/pkg/scanner" +) + +func (l *LanguageService) ProvideFoldingRange(ctx context.Context, documentURI lsproto.DocumentUri) (lsproto.FoldingRangeResponse, error) { + _, sourceFile := l.getProgramAndFile(documentURI) + res := l.addNodeOutliningSpans(ctx, sourceFile) + res = append(res, l.addRegionOutliningSpans(sourceFile)...) + slices.SortFunc(res, func(a, b *lsproto.FoldingRange) int { + if c := cmp.Compare(a.StartLine, b.StartLine); c != 0 { + return c + } + return cmp.Compare(*a.StartCharacter, *b.StartCharacter) + }) + return lsproto.FoldingRangesOrNull{FoldingRanges: &res}, nil +} + +func (l *LanguageService) addNodeOutliningSpans(ctx context.Context, sourceFile *ast.SourceFile) []*lsproto.FoldingRange { + depthRemaining := 40 + current := 0 + + statements := sourceFile.Statements + n := len(statements.Nodes) + foldingRange := make([]*lsproto.FoldingRange, 0, 40) + for current < n { + for current < n && !ast.IsAnyImportSyntax(statements.Nodes[current]) { + foldingRange = append(foldingRange, visitNode(ctx, statements.Nodes[current], depthRemaining, sourceFile, l)...) + current++ + } + if current == n { + break + } + firstImport := current + for current < n && ast.IsAnyImportSyntax(statements.Nodes[current]) { + foldingRange = append(foldingRange, visitNode(ctx, statements.Nodes[current], depthRemaining, sourceFile, l)...) + current++ + } + lastImport := current - 1 + if lastImport != firstImport { + foldingRangeKind := lsproto.FoldingRangeKindImports + foldingRange = append(foldingRange, createFoldingRangeFromBounds( + astnav.GetStartOfNode(astnav.FindChildOfKind(statements.Nodes[firstImport], + ast.KindImportKeyword, sourceFile), sourceFile, false /*includeJSDoc*/), + statements.Nodes[lastImport].End(), + foldingRangeKind, + sourceFile, + l)) + } + } + + // Visit the EOF Token so that comments which aren't attached to statements are included. + foldingRange = append(foldingRange, visitNode(ctx, sourceFile.EndOfFileToken, depthRemaining, sourceFile, l)...) + return foldingRange +} + +func (l *LanguageService) addRegionOutliningSpans(sourceFile *ast.SourceFile) []*lsproto.FoldingRange { + regions := make([]*lsproto.FoldingRange, 0, 40) + out := make([]*lsproto.FoldingRange, 0, 40) + lineStarts := scanner.GetECMALineStarts(sourceFile) + for _, currentLineStart := range lineStarts { + lineEnd := getLineEndOfPosition(sourceFile, int(currentLineStart)) + lineText := sourceFile.Text()[currentLineStart:lineEnd] + result := parseRegionDelimiter(lineText) + if result == nil || isInComment(sourceFile, int(currentLineStart), astnav.GetTokenAtPosition(sourceFile, int(currentLineStart))) != nil { + continue + } + + if result.isStart { + commentStart := l.createLspPosition(strings.Index(sourceFile.Text()[currentLineStart:lineEnd], "//")+int(currentLineStart), sourceFile) + foldingRangeKindRegion := lsproto.FoldingRangeKindRegion + collapsedText := "#region" + if result.name != "" { + collapsedText = result.name + } + // Our spans start out with some initial data. + // On every `#endregion`, we'll come back to these `FoldingRange`s + // and fill in their EndLine/EndCharacter. + regions = append(regions, &lsproto.FoldingRange{ + StartLine: commentStart.Line, + StartCharacter: &commentStart.Character, + Kind: &foldingRangeKindRegion, + CollapsedText: &collapsedText, + }) + } else { + if len(regions) > 0 { + region := regions[len(regions)-1] + regions = regions[:len(regions)-1] + endingPosition := l.createLspPosition(lineEnd, sourceFile) + region.EndLine = endingPosition.Line + region.EndCharacter = &endingPosition.Character + out = append(out, region) + } + } + } + return out +} + +func visitNode(ctx context.Context, n *ast.Node, depthRemaining int, sourceFile *ast.SourceFile, l *LanguageService) []*lsproto.FoldingRange { + if depthRemaining == 0 { + return nil + } + if ctx.Err() != nil { + return nil + } + foldingRange := make([]*lsproto.FoldingRange, 0, 40) + if (!ast.IsBinaryExpression(n) && ast.IsDeclaration(n)) || ast.IsVariableStatement(n) || ast.IsReturnStatement(n) || ast.IsCallOrNewExpression(n) || n.Kind == ast.KindEndOfFile { + foldingRange = append(foldingRange, addOutliningForLeadingCommentsForNode(ctx, n, sourceFile, l)...) + } + if ast.IsFunctionLike(n) && n.Parent != nil && ast.IsBinaryExpression(n.Parent) && n.Parent.AsBinaryExpression().Left != nil && ast.IsPropertyAccessExpression(n.Parent.AsBinaryExpression().Left) { + foldingRange = append(foldingRange, addOutliningForLeadingCommentsForNode(ctx, n.Parent.AsBinaryExpression().Left, sourceFile, l)...) + } + if ast.IsBlock(n) { + statements := n.AsBlock().Statements + if statements != nil { + foldingRange = append(foldingRange, addOutliningForLeadingCommentsForPos(ctx, statements.End(), sourceFile, l)...) + } + } + if ast.IsModuleBlock(n) { + statements := n.AsModuleBlock().Statements + if statements != nil { + foldingRange = append(foldingRange, addOutliningForLeadingCommentsForPos(ctx, statements.End(), sourceFile, l)...) + } + } + if ast.IsClassLike(n) || ast.IsInterfaceDeclaration(n) { + var members *ast.NodeList + if ast.IsClassDeclaration(n) { + members = n.AsClassDeclaration().Members + } else if ast.IsClassExpression(n) { + members = n.AsClassExpression().Members + } else { + members = n.AsInterfaceDeclaration().Members + } + if members != nil { + foldingRange = append(foldingRange, addOutliningForLeadingCommentsForPos(ctx, members.End(), sourceFile, l)...) + } + } + + span := getOutliningSpanForNode(n, sourceFile, l) + if span != nil { + foldingRange = append(foldingRange, span) + } + + depthRemaining-- + if ast.IsCallExpression(n) { + depthRemaining++ + expressionNodes := visitNode(ctx, n.Expression(), depthRemaining, sourceFile, l) + if expressionNodes != nil { + foldingRange = append(foldingRange, expressionNodes...) + } + depthRemaining-- + for _, arg := range n.Arguments() { + if arg != nil { + foldingRange = append(foldingRange, visitNode(ctx, arg, depthRemaining, sourceFile, l)...) + } + } + typeArguments := n.TypeArguments() + for _, typeArg := range typeArguments { + if typeArg != nil { + foldingRange = append(foldingRange, visitNode(ctx, typeArg, depthRemaining, sourceFile, l)...) + } + } + } else if ast.IsIfStatement(n) && n.AsIfStatement().ElseStatement != nil && ast.IsIfStatement(n.AsIfStatement().ElseStatement) { + // Consider an 'else if' to be on the same depth as the 'if'. + ifStatement := n.AsIfStatement() + expressionNodes := visitNode(ctx, n.Expression(), depthRemaining, sourceFile, l) + if expressionNodes != nil { + foldingRange = append(foldingRange, expressionNodes...) + } + thenNode := visitNode(ctx, ifStatement.ThenStatement, depthRemaining, sourceFile, l) + if thenNode != nil { + foldingRange = append(foldingRange, thenNode...) + } + depthRemaining++ + elseNode := visitNode(ctx, ifStatement.ElseStatement, depthRemaining, sourceFile, l) + if elseNode != nil { + foldingRange = append(foldingRange, elseNode...) + } + depthRemaining-- + } else { + visit := func(node *ast.Node) bool { + childNode := visitNode(ctx, node, depthRemaining, sourceFile, l) + if childNode != nil { + foldingRange = append(foldingRange, childNode...) + } + return false + } + n.ForEachChild(visit) + } + depthRemaining++ + return foldingRange +} + +func addOutliningForLeadingCommentsForNode(ctx context.Context, n *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) []*lsproto.FoldingRange { + if ast.IsJsxText(n) { + return nil + } + return addOutliningForLeadingCommentsForPos(ctx, n.Pos(), sourceFile, l) +} + +func addOutliningForLeadingCommentsForPos(ctx context.Context, pos int, sourceFile *ast.SourceFile, l *LanguageService) []*lsproto.FoldingRange { + p := &printer.EmitContext{} + foldingRange := make([]*lsproto.FoldingRange, 0, 40) + firstSingleLineCommentStart := -1 + lastSingleLineCommentEnd := -1 + singleLineCommentCount := 0 + foldingRangeKindComment := lsproto.FoldingRangeKindComment + + combineAndAddMultipleSingleLineComments := func() *lsproto.FoldingRange { + // Only outline spans of two or more consecutive single line comments + if singleLineCommentCount > 1 { + return createFoldingRangeFromBounds(firstSingleLineCommentStart, lastSingleLineCommentEnd, foldingRangeKindComment, sourceFile, l) + } + return nil + } + + sourceText := sourceFile.Text() + for comment := range scanner.GetLeadingCommentRanges(&printer.NewNodeFactory(p).NodeFactory, sourceText, pos) { + commentPos := comment.Pos() + commentEnd := comment.End() + + if ctx.Err() != nil { + return nil + } + switch comment.Kind { + case ast.KindSingleLineCommentTrivia: + // never fold region delimiters into single-line comment regions + commentText := sourceText[commentPos:commentEnd] + if parseRegionDelimiter(commentText) != nil { + comments := combineAndAddMultipleSingleLineComments() + if comments != nil { + foldingRange = append(foldingRange, comments) + } + singleLineCommentCount = 0 + break + } + + // For single line comments, combine consecutive ones (2 or more) into + // a single span from the start of the first till the end of the last + if singleLineCommentCount == 0 { + firstSingleLineCommentStart = commentPos + } + lastSingleLineCommentEnd = commentEnd + singleLineCommentCount++ + break + case ast.KindMultiLineCommentTrivia: + comments := combineAndAddMultipleSingleLineComments() + if comments != nil { + foldingRange = append(foldingRange, comments) + } + foldingRange = append(foldingRange, createFoldingRangeFromBounds(commentPos, commentEnd, foldingRangeKindComment, sourceFile, l)) + singleLineCommentCount = 0 + break + default: + debug.AssertNever(comment.Kind) + } + } + addedComments := combineAndAddMultipleSingleLineComments() + if addedComments != nil { + foldingRange = append(foldingRange, addedComments) + } + return foldingRange +} + +type regionDelimiterResult struct { + isStart bool + name string +} + +func parseRegionDelimiter(lineText string) *regionDelimiterResult { + // We trim the leading whitespace and // without the regex since the + // multiple potential whitespace matches can make for some gnarly backtracking behavior + lineText = strings.TrimLeftFunc(lineText, unicode.IsSpace) + if !strings.HasPrefix(lineText, "//") { + return nil + } + lineText = strings.TrimSpace(lineText[2:]) + lineText = strings.TrimSuffix(lineText, "\r") + if !strings.HasPrefix(lineText, "#") { + return nil + } + lineText = lineText[1:] + isStart := true + if strings.HasPrefix(lineText, "end") { + isStart = false + lineText = lineText[3:] + } + if !strings.HasPrefix(lineText, "region") { + return nil + } + lineText = lineText[6:] + return ®ionDelimiterResult{ + isStart: isStart, + name: strings.TrimSpace(lineText), + } +} + +func getOutliningSpanForNode(n *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + switch n.Kind { + case ast.KindBlock: + if ast.IsFunctionLike(n.Parent) { + return functionSpan(n.Parent, n, sourceFile, l) + } + // Check if the block is standalone, or 'attached' to some parent statement. + // If the latter, we want to collapse the block, but consider its hint span + // to be the entire span of the parent. + switch n.Parent.Kind { + case ast.KindDoStatement, ast.KindForInStatement, ast.KindForOfStatement, ast.KindForStatement, ast.KindIfStatement, ast.KindWhileStatement, ast.KindWithStatement, ast.KindCatchClause: + return spanForNode(n, ast.KindOpenBraceToken, true /*useFullStart*/, sourceFile, l) + case ast.KindTryStatement: + // Could be the try-block, or the finally-block. + tryStatement := n.Parent.AsTryStatement() + if tryStatement.TryBlock == n { + return spanForNode(n, ast.KindOpenBraceToken, true /*useFullStart*/, sourceFile, l) + } else if tryStatement.FinallyBlock == n { + if span := spanForNode(n, ast.KindOpenBraceToken, true /*useFullStart*/, sourceFile, l); span != nil { + return span + } + } + fallthrough + default: + // Block was a standalone block. In this case we want to only collapse + // the span of the block, independent of any parent span. + return createFoldingRange(l.createLspRangeFromNode(n, sourceFile), "", "") + } + case ast.KindModuleBlock: + return spanForNode(n, ast.KindOpenBraceToken, true /*useFullStart*/, sourceFile, l) + case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration, ast.KindEnumDeclaration, ast.KindCaseBlock, ast.KindTypeLiteral, ast.KindObjectBindingPattern: + return spanForNode(n, ast.KindOpenBraceToken, true /*useFullStart*/, sourceFile, l) + case ast.KindTupleType: + return spanForNode(n, ast.KindOpenBracketToken, !ast.IsTupleTypeNode(n.Parent) /*useFullStart*/, sourceFile, l) + case ast.KindCaseClause, ast.KindDefaultClause: + return spanForNodeArray(n.AsCaseOrDefaultClause().Statements, sourceFile, l) + case ast.KindObjectLiteralExpression: + return spanForNode(n, ast.KindOpenBraceToken, !ast.IsArrayLiteralExpression(n.Parent) && !ast.IsCallExpression(n.Parent) /*useFullStart*/, sourceFile, l) + case ast.KindArrayLiteralExpression: + return spanForNode(n, ast.KindOpenBracketToken, !ast.IsArrayLiteralExpression(n.Parent) && !ast.IsCallExpression(n.Parent) /*useFullStart*/, sourceFile, l) + case ast.KindJsxElement, ast.KindJsxFragment: + return spanForJSXElement(n, sourceFile, l) + case ast.KindJsxSelfClosingElement, ast.KindJsxOpeningElement: + return spanForJSXAttributes(n, sourceFile, l) + case ast.KindTemplateExpression, ast.KindNoSubstitutionTemplateLiteral: + return spanForTemplateLiteral(n, sourceFile, l) + case ast.KindArrayBindingPattern: + return spanForNode(n, ast.KindOpenBracketToken, !ast.IsBindingElement(n.Parent) /*useFullStart*/, sourceFile, l) + case ast.KindArrowFunction: + return spanForArrowFunction(n, sourceFile, l) + case ast.KindCallExpression: + return spanForCallExpression(n, sourceFile, l) + case ast.KindParenthesizedExpression: + return spanForParenthesizedExpression(n, sourceFile, l) + case ast.KindNamedImports, ast.KindNamedExports, ast.KindImportAttributes: + return spanForImportExportElements(n, sourceFile, l) + } + return nil +} + +func spanForImportExportElements(node *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + var elements *ast.NodeList + switch node.Kind { + case ast.KindNamedImports: + elements = node.AsNamedImports().Elements + case ast.KindNamedExports: + elements = node.AsNamedExports().Elements + case ast.KindImportAttributes: + elements = node.AsImportAttributes().Attributes + } + if elements == nil || len(elements.Nodes) == 0 { + return nil + } + openToken := astnav.FindChildOfKind(node, ast.KindOpenBraceToken, sourceFile) + closeToken := astnav.FindChildOfKind(node, ast.KindCloseBraceToken, sourceFile) + if openToken == nil || closeToken == nil || printer.PositionsAreOnSameLine(openToken.Pos(), closeToken.Pos(), sourceFile) { + return nil + } + return rangeBetweenTokens(openToken, closeToken, sourceFile, false /*useFullStart*/, l) +} + +func spanForParenthesizedExpression(node *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + start := astnav.GetStartOfNode(node, sourceFile, false /*includeJSDoc*/) + if printer.PositionsAreOnSameLine(start, node.End(), sourceFile) { + return nil + } + textRange := l.createLspRangeFromBounds(start, node.End(), sourceFile) + return createFoldingRange(textRange, "", "") +} + +func spanForCallExpression(node *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + if node.AsCallExpression().Arguments == nil || len(node.AsCallExpression().Arguments.Nodes) == 0 { + return nil + } + openToken := astnav.FindChildOfKind(node, ast.KindOpenParenToken, sourceFile) + closeToken := astnav.FindChildOfKind(node, ast.KindCloseParenToken, sourceFile) + if openToken == nil || closeToken == nil || printer.PositionsAreOnSameLine(openToken.Pos(), closeToken.Pos(), sourceFile) { + return nil + } + + return rangeBetweenTokens(openToken, closeToken, sourceFile, true /*useFullStart*/, l) +} + +func spanForArrowFunction(node *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + arrowFunctionNode := node.AsArrowFunction() + if ast.IsBlock(arrowFunctionNode.Body) || ast.IsParenthesizedExpression(arrowFunctionNode.Body) || printer.PositionsAreOnSameLine(arrowFunctionNode.Body.Pos(), arrowFunctionNode.Body.End(), sourceFile) { + return nil + } + textRange := l.createLspRangeFromBounds(arrowFunctionNode.Body.Pos(), arrowFunctionNode.Body.End(), sourceFile) + return createFoldingRange(textRange, "", "") +} + +func spanForTemplateLiteral(node *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + if node.Kind == ast.KindNoSubstitutionTemplateLiteral && len(node.Text()) == 0 { + return nil + } + return createFoldingRangeFromBounds(astnav.GetStartOfNode(node, sourceFile, false /*includeJSDoc*/), node.End(), "", sourceFile, l) +} + +func spanForJSXElement(node *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + if node.Kind == ast.KindJsxElement { + jsxElement := node.AsJsxElement() + textRange := l.createLspRangeFromBounds(astnav.GetStartOfNode(jsxElement.OpeningElement, sourceFile, false /*includeJSDoc*/), jsxElement.ClosingElement.End(), sourceFile) + tagName := jsxElement.OpeningElement.TagName().Text() + bannerText := "<" + tagName + ">..." + return createFoldingRange(textRange, "", bannerText) + } + // JsxFragment + jsxFragment := node.AsJsxFragment() + textRange := l.createLspRangeFromBounds(astnav.GetStartOfNode(jsxFragment.OpeningFragment, sourceFile, false /*includeJSDoc*/), jsxFragment.ClosingFragment.End(), sourceFile) + return createFoldingRange(textRange, "", "<>...") +} + +func spanForJSXAttributes(node *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + var attributes *ast.JsxAttributesNode + if node.Kind == ast.KindJsxSelfClosingElement { + attributes = node.AsJsxSelfClosingElement().Attributes + } else { + attributes = node.AsJsxOpeningElement().Attributes + } + if len(attributes.Properties()) == 0 { + return nil + } + return createFoldingRangeFromBounds(astnav.GetStartOfNode(node, sourceFile, false /*includeJSDoc*/), node.End(), "", sourceFile, l) +} + +func spanForNodeArray(statements *ast.NodeList, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + if statements != nil && len(statements.Nodes) != 0 { + return createFoldingRange(l.createLspRangeFromBounds(statements.Pos(), statements.End(), sourceFile), "", "") + } + return nil +} + +func spanForNode(node *ast.Node, open ast.Kind, useFullStart bool, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + closeBrace := ast.KindCloseBraceToken + if open != ast.KindOpenBraceToken { + closeBrace = ast.KindCloseBracketToken + } + openToken := astnav.FindChildOfKind(node, open, sourceFile) + closeToken := astnav.FindChildOfKind(node, closeBrace, sourceFile) + if openToken != nil && closeToken != nil { + return rangeBetweenTokens(openToken, closeToken, sourceFile, useFullStart, l) + } + return nil +} + +func rangeBetweenTokens(openToken *ast.Node, closeToken *ast.Node, sourceFile *ast.SourceFile, useFullStart bool, l *LanguageService) *lsproto.FoldingRange { + var textRange *lsproto.Range + if useFullStart { + textRange = l.createLspRangeFromBounds(openToken.Pos(), closeToken.End(), sourceFile) + } else { + textRange = l.createLspRangeFromBounds(astnav.GetStartOfNode(openToken, sourceFile, false /*includeJSDoc*/), closeToken.End(), sourceFile) + } + return createFoldingRange(textRange, "", "") +} + +func createFoldingRange(textRange *lsproto.Range, foldingRangeKind lsproto.FoldingRangeKind, collapsedText string) *lsproto.FoldingRange { + if collapsedText == "" { + defaultText := "..." + collapsedText = defaultText + } + var kind *lsproto.FoldingRangeKind + if foldingRangeKind != "" { + kind = &foldingRangeKind + } + return &lsproto.FoldingRange{ + StartLine: textRange.Start.Line, + StartCharacter: &textRange.Start.Character, + EndLine: textRange.End.Line, + EndCharacter: &textRange.End.Character, + Kind: kind, + CollapsedText: &collapsedText, + } +} + +func createFoldingRangeFromBounds(pos int, end int, foldingRangeKind lsproto.FoldingRangeKind, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + return createFoldingRange(l.createLspRangeFromBounds(pos, end, sourceFile), foldingRangeKind, "") +} + +func functionSpan(node *ast.Node, body *ast.Node, sourceFile *ast.SourceFile, l *LanguageService) *lsproto.FoldingRange { + openToken := tryGetFunctionOpenToken(node, body, sourceFile) + closeToken := astnav.FindChildOfKind(body, ast.KindCloseBraceToken, sourceFile) + if openToken != nil && closeToken != nil { + return rangeBetweenTokens(openToken, closeToken, sourceFile, true /*useFullStart*/, l) + } + return nil +} + +func tryGetFunctionOpenToken(node *ast.SignatureDeclaration, body *ast.Node, sourceFile *ast.SourceFile) *ast.Node { + if isNodeArrayMultiLine(node.Parameters(), sourceFile) { + openParenToken := astnav.FindChildOfKind(node, ast.KindOpenParenToken, sourceFile) + if openParenToken != nil { + return openParenToken + } + } + return astnav.FindChildOfKind(body, ast.KindOpenBraceToken, sourceFile) +} + +func isNodeArrayMultiLine(list []*ast.Node, sourceFile *ast.SourceFile) bool { + if len(list) == 0 { + return false + } + return !printer.PositionsAreOnSameLine(list[0].Pos(), list[len(list)-1].End(), sourceFile) +} diff --git a/pkg/ls/inlay_hints.go b/pkg/ls/inlay_hints.go index db9db059b..8b68d07f4 100644 --- a/pkg/ls/inlay_hints.go +++ b/pkg/ls/inlay_hints.go @@ -104,7 +104,7 @@ func (s *inlayHintState) visit(node *ast.Node) bool { // FunctionDeclaration | MethodDeclaration | GetAccessorDeclaration | FunctionExpression | ArrowFunction func (s *inlayHintState) visitFunctionDeclarationLikeForReturnType(decl *ast.FunctionLikeDeclaration) { if ast.IsArrowFunction(decl) { - if findChildOfKind(decl, ast.KindOpenParenToken, s.file) == nil { + if astnav.FindChildOfKind(decl, ast.KindOpenParenToken, s.file) == nil { return } } @@ -889,7 +889,7 @@ func (s *inlayHintState) leadingCommentsContainsParameterName(node *ast.Node, na } func (s *inlayHintState) getTypeAnnotationPosition(decl *ast.FunctionLikeDeclaration) int { - closeParenToken := findChildOfKind(decl, ast.KindCloseParenToken, s.file) + closeParenToken := astnav.FindChildOfKind(decl, ast.KindCloseParenToken, s.file) if closeParenToken != nil { return closeParenToken.End() } diff --git a/pkg/ls/utilities.go b/pkg/ls/utilities.go index 72dbcdab3..d490d2bf0 100644 --- a/pkg/ls/utilities.go +++ b/pkg/ls/utilities.go @@ -166,62 +166,7 @@ func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) } func hasChildOfKind(containingNode *ast.Node, kind ast.Kind, sourceFile *ast.SourceFile) bool { - return findChildOfKind(containingNode, kind, sourceFile) != nil -} - -func findChildOfKind(containingNode *ast.Node, kind ast.Kind, sourceFile *ast.SourceFile) *ast.Node { - lastNodePos := containingNode.Pos() - scanner := scanner.GetScannerForSourceFile(sourceFile, lastNodePos) - - var foundChild *ast.Node - visitNode := func(node *ast.Node) bool { - if node == nil || node.Flags&ast.NodeFlagsReparsed != 0 { - return false - } - // Look for child in preceding tokens. - startPos := lastNodePos - for startPos < node.Pos() { - tokenKind := scanner.Token() - tokenFullStart := scanner.TokenFullStart() - tokenEnd := scanner.TokenEnd() - token := sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, containingNode) - if tokenKind == kind { - foundChild = token - return true - } - startPos = tokenEnd - scanner.Scan() - } - if node.Kind == kind { - foundChild = node - return true - } - - lastNodePos = node.End() - scanner.ResetPos(lastNodePos) - return false - } - - ast.ForEachChildAndJSDoc(containingNode, sourceFile, visitNode) - - if foundChild != nil { - return foundChild - } - - // Look for child in trailing tokens. - startPos := lastNodePos - for startPos < containingNode.End() { - tokenKind := scanner.Token() - tokenFullStart := scanner.TokenFullStart() - tokenEnd := scanner.TokenEnd() - token := sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, containingNode) - if tokenKind == kind { - return token - } - startPos = tokenEnd - scanner.Scan() - } - return nil + return astnav.FindChildOfKind(containingNode, kind, sourceFile) != nil } type PossibleTypeArgumentInfo struct { @@ -1095,10 +1040,10 @@ func getAdjustedLocationForDeclaration(node *ast.Node, forRename bool, sourceFil return core.Find(node.ModifierNodes(), func(*ast.Node) bool { return node.Kind == ast.KindDefaultKeyword }) case ast.KindClassExpression: // for class expressions, use the `class` keyword when the class is unnamed - return findChildOfKind(node, ast.KindClassKeyword, sourceFile) + return astnav.FindChildOfKind(node, ast.KindClassKeyword, sourceFile) case ast.KindFunctionExpression: // for function expressions, use the `function` keyword when the function is unnamed - return findChildOfKind(node, ast.KindFunctionKeyword, sourceFile) + return astnav.FindChildOfKind(node, ast.KindFunctionKeyword, sourceFile) case ast.KindConstructor: return node } diff --git a/pkg/lsp/server.go b/pkg/lsp/server.go index ff71fadc5..9e0ec3bd2 100644 --- a/pkg/lsp/server.go +++ b/pkg/lsp/server.go @@ -519,6 +519,7 @@ var handlers = sync.OnceValue(func() handlerMap { registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCodeLensInfo, (*Server).handleCodeLens) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCodeActionInfo, (*Server).handleCodeAction) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentPrepareCallHierarchyInfo, (*Server).handlePrepareCallHierarchy) + registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentFoldingRangeInfo, (*Server).handleFoldingRange) registerMultiProjectReferenceRequestHandler(handlers, lsproto.TextDocumentReferencesInfo, (*Server).handleReferences, combineReferences) registerMultiProjectReferenceRequestHandler(handlers, lsproto.TextDocumentRenameInfo, (*Server).handleRename, combineRenameResponse) @@ -526,6 +527,9 @@ var handlers = sync.OnceValue(func() handlerMap { registerRequestHandler(handlers, lsproto.CallHierarchyIncomingCallsInfo, (*Server).handleCallHierarchyIncomingCalls) registerRequestHandler(handlers, lsproto.CallHierarchyOutgoingCallsInfo, (*Server).handleCallHierarchyOutgoingCalls) + registerRequestHandler(handlers, lsproto.CallHierarchyIncomingCallsInfo, (*Server).handleCallHierarchyIncomingCalls) + registerRequestHandler(handlers, lsproto.CallHierarchyOutgoingCallsInfo, (*Server).handleCallHierarchyOutgoingCalls) + registerRequestHandler(handlers, lsproto.WorkspaceSymbolInfo, (*Server).handleWorkspaceSymbol) registerRequestHandler(handlers, lsproto.CompletionItemResolveInfo, (*Server).handleCompletionItemResolve) registerRequestHandler(handlers, lsproto.CodeLensResolveInfo, (*Server).handleCodeLensResolve) @@ -939,6 +943,9 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ DocumentSymbolProvider: &lsproto.BooleanOrDocumentSymbolOptions{ Boolean: ptrTo(true), }, + FoldingRangeProvider: &lsproto.BooleanOrFoldingRangeOptionsOrFoldingRangeRegistrationOptions{ + Boolean: ptrTo(true), + }, RenameProvider: &lsproto.BooleanOrRenameOptions{ Boolean: ptrTo(true), }, @@ -1133,6 +1140,10 @@ func (s *Server) handleSignatureHelp(ctx context.Context, languageService *ls.La ) } +func (s *Server) handleFoldingRange(ctx context.Context, ls *ls.LanguageService, params *lsproto.FoldingRangeParams) (lsproto.FoldingRangeResponse, error) { + return ls.ProvideFoldingRange(ctx, params.TextDocument.Uri) +} + func (s *Server) handleDefinition(ctx context.Context, ls *ls.LanguageService, params *lsproto.DefinitionParams) (lsproto.DefinitionResponse, error) { return ls.ProvideDefinition(ctx, params.TextDocument.Uri, params.Position) } diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 35b8fcd30..cd9977647 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -5059,7 +5059,7 @@ func (p *Printer) emitCommentsBeforeToken(token ast.Kind, pos int, contextNode * if contextNode.Pos() != startPos { indentLeading := flags&tefIndentLeadingComments != 0 - needsIndent := indentLeading && p.currentSourceFile != nil && !positionsAreOnSameLine(startPos, pos, p.currentSourceFile) + needsIndent := indentLeading && p.currentSourceFile != nil && !PositionsAreOnSameLine(startPos, pos, p.currentSourceFile) p.increaseIndentIf(needsIndent) p.emitLeadingComments(startPos, false /*elided*/) p.decreaseIndentIf(needsIndent) diff --git a/pkg/printer/utilities.go b/pkg/printer/utilities.go index 9a5334bbf..14631107e 100644 --- a/pkg/printer/utilities.go +++ b/pkg/printer/utilities.go @@ -335,7 +335,7 @@ func rangeIsOnSingleLine(r core.TextRange, sourceFile *ast.SourceFile) bool { } func rangeStartPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { - return positionsAreOnSameLine( + return PositionsAreOnSameLine( getStartPositionOfRange(range1, sourceFile, false /*includeComments*/), getStartPositionOfRange(range2, sourceFile, false /*includeComments*/), sourceFile, @@ -343,15 +343,15 @@ func rangeStartPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRan } func rangeEndPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { - return positionsAreOnSameLine(range1.End(), range2.End(), sourceFile) + return PositionsAreOnSameLine(range1.End(), range2.End(), sourceFile) } func rangeStartIsOnSameLineAsRangeEnd(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { - return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile, false /*includeComments*/), range2.End(), sourceFile) + return PositionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile, false /*includeComments*/), range2.End(), sourceFile) } func rangeEndIsOnSameLineAsRangeStart(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { - return positionsAreOnSameLine(range1.End(), getStartPositionOfRange(range2, sourceFile, false /*includeComments*/), sourceFile) + return PositionsAreOnSameLine(range1.End(), getStartPositionOfRange(range2, sourceFile, false /*includeComments*/), sourceFile) } func getStartPositionOfRange(r core.TextRange, sourceFile *ast.SourceFile, includeComments bool) int { @@ -361,7 +361,7 @@ func getStartPositionOfRange(r core.TextRange, sourceFile *ast.SourceFile, inclu return scanner.SkipTriviaEx(sourceFile.Text(), r.Pos(), &scanner.SkipTriviaOptions{StopAtComments: includeComments}) } -func positionsAreOnSameLine(pos1 int, pos2 int, sourceFile *ast.SourceFile) bool { +func PositionsAreOnSameLine(pos1 int, pos2 int, sourceFile *ast.SourceFile) bool { return GetLinesBetweenPositions(sourceFile, pos1, pos2) == 0 } diff --git a/pkg/testrunner/compiler_runner.go b/pkg/testrunner/compiler_runner.go index 4601a4fa3..061454824 100644 --- a/pkg/testrunner/compiler_runner.go +++ b/pkg/testrunner/compiler_runner.go @@ -350,19 +350,8 @@ func newCompilerTest( } } -var concurrentSkippedErrorBaselines = map[string]string{ - "typeOnlyMerge2.ts": "Type-only merging is not detected when files are checked on different checkers.", - "typeOnlyMerge3.ts": "Type-only merging is not detected when files are checked on different checkers.", -} - func (c *compilerTest) verifyDiagnostics(t *testing.T, suiteName string, isSubmodule bool) { t.Run("error", func(t *testing.T) { - if !testutil.TestProgramIsSingleThreaded() { - if msg, ok := concurrentSkippedErrorBaselines[c.basename]; ok { - t.Skipf("Skipping in concurrent mode: %s", msg) - } - } - defer testutil.RecoverAndFail(t, "Panic on creating error baseline for test "+c.filename) files := core.Concatenate(c.tsConfigFiles, core.Concatenate(c.toBeCompiled, c.otherFiles)) tsbaseline.DoErrorBaseline(t, c.configuredName, files, c.result.Diagnostics, c.result.Options.Pretty.IsTrue(), baseline.Options{ diff --git a/pkg/tspath/path.go b/pkg/tspath/path.go index 4c95d73b9..55869b2b5 100644 --- a/pkg/tspath/path.go +++ b/pkg/tspath/path.go @@ -334,6 +334,12 @@ func GetNormalizedPathComponents(path string, currentDirectory string) []string return reducePathComponents(GetPathComponents(path, currentDirectory)) } +func GetNormalizedAbsolutePathWithoutRoot(fileName string, currentDirectory string) string { + absolutePath := GetNormalizedAbsolutePath(fileName, currentDirectory) + rootLength := GetRootLength(absolutePath) + return absolutePath[rootLength:] +} + func GetNormalizedAbsolutePath(fileName string, currentDirectory string) string { rootLength := GetRootLength(fileName) if rootLength == 0 && currentDirectory != "" { diff --git a/pkg/tspath/path_test.go b/pkg/tspath/path_test.go index 85d7829de..711030a1c 100644 --- a/pkg/tspath/path_test.go +++ b/pkg/tspath/path_test.go @@ -417,6 +417,14 @@ func TestGetNormalizedAbsolutePath(t *testing.T) { assert.Equal(t, GetNormalizedAbsolutePath("\\\\a\\b\\\\c", ""), "//a/b/c") } +func TestGetNormalizedAbsolutePathWithoutRoot(t *testing.T) { + t.Parallel() + + assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("/a/b/c.txt", "/a/b"), "a/b/c.txt") + assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("c:/work/hello.txt", "c:/work"), "work/hello.txt") + assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("c:/work/hello.txt", "d:/worspaces"), "work/hello.txt") +} + var getNormalizedAbsolutePathTests = map[string][][]string{ "non-normalized inputs": { {"/.", ""}, diff --git a/testdata/baselines/reference/submodule/compiler/incrementalInvalid.errors.txt b/testdata/baselines/reference/submodule/compiler/incrementalInvalid.errors.txt new file mode 100644 index 000000000..29b9039f3 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/incrementalInvalid.errors.txt @@ -0,0 +1,8 @@ +error TS5074: Option '--incremental' is only valid with a known configuration file (like 'tsconfig.json') or when '--tsBuildInfoFile' is explicitly provided. + + +!!! error TS5074: Option '--incremental' is only valid with a known configuration file (like 'tsconfig.json') or when '--tsBuildInfoFile' is explicitly provided. +==== incrementalInvalid.ts (0 errors) ==== + const x = 10; + + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/incrementalInvalid.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/incrementalInvalid.errors.txt.diff index 0e9d82e83..ff26767ed 100644 --- a/testdata/baselines/reference/submodule/compiler/incrementalInvalid.errors.txt.diff +++ b/testdata/baselines/reference/submodule/compiler/incrementalInvalid.errors.txt.diff @@ -5,8 +5,10 @@ - - -!!! error TS5074: Option '--incremental' can only be specified using tsconfig, emitting to single file or when option '--tsBuildInfoFile' is specified. --==== incrementalInvalid.ts (0 errors) ==== -- const x = 10; -- -- -+ \ No newline at end of file ++error TS5074: Option '--incremental' is only valid with a known configuration file (like 'tsconfig.json') or when '--tsBuildInfoFile' is explicitly provided. ++ ++ ++!!! error TS5074: Option '--incremental' is only valid with a known configuration file (like 'tsconfig.json') or when '--tsBuildInfoFile' is explicitly provided. + ==== incrementalInvalid.ts (0 errors) ==== + const x = 10; + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/importEquals3.errors.txt b/testdata/baselines/reference/submodule/conformance/importEquals3.errors.txt index 1d66b070d..5a5de9539 100644 --- a/testdata/baselines/reference/submodule/conformance/importEquals3.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/importEquals3.errors.txt @@ -1,7 +1,6 @@ b.ts(2,12): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. b.ts(3,13): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. c.ts(2,12): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. -c.ts(3,13): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. ==== a.ts (0 errors) ==== @@ -21,16 +20,13 @@ c.ts(3,13): error TS1380: An import alias cannot reference a declaration that wa const x = 0; export { a, A, x }; -==== c.ts (2 errors) ==== +==== c.ts (1 errors) ==== import * as b from './b'; import A = b.a.A; // Error ~~~~~ !!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. !!! related TS1376 b.ts:1:18: 'a' was imported here. import AA = b.A; // Error - ~~~ -!!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. -!!! related TS1376 b.ts:1:18: 'a' was imported here. import x = b.x; console.log(x); diff --git a/testdata/baselines/reference/submodule/conformance/importEquals3.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/importEquals3.errors.txt.diff new file mode 100644 index 000000000..12ebbe21c --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/importEquals3.errors.txt.diff @@ -0,0 +1,28 @@ +--- old.importEquals3.errors.txt ++++ new.importEquals3.errors.txt +@@= skipped -0, +0 lines =@@ + b.ts(2,12): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. + b.ts(3,13): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. + c.ts(2,12): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +-c.ts(3,13): error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. + + + ==== a.ts (0 errors) ==== +@@= skipped -20, +19 lines =@@ + const x = 0; + export { a, A, x }; + +-==== c.ts (2 errors) ==== ++==== c.ts (1 errors) ==== + import * as b from './b'; + import A = b.a.A; // Error + ~~~~~ + !!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. + !!! related TS1376 b.ts:1:18: 'a' was imported here. + import AA = b.A; // Error +- ~~~ +-!!! error TS1380: An import alias cannot reference a declaration that was imported using 'import type'. +-!!! related TS1376 b.ts:1:18: 'a' was imported here. + + import x = b.x; + console.log(x); \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/typeOnlyMerge3.errors.txt b/testdata/baselines/reference/submodule/conformance/typeOnlyMerge3.errors.txt index 6b50da4e6..0ad257fcf 100644 --- a/testdata/baselines/reference/submodule/conformance/typeOnlyMerge3.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/typeOnlyMerge3.errors.txt @@ -1,7 +1,4 @@ b.ts(1,10): error TS2440: Import declaration conflicts with local declaration of 'A'. -c.ts(2,1): error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. -c.ts(3,1): error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. -c.ts(4,1): error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. c.ts(4,1): error TS2349: This expression is not callable. Type 'typeof A' has no call signatures. @@ -19,21 +16,12 @@ c.ts(4,1): error TS2349: This expression is not callable. } export { A }; -==== c.ts (4 errors) ==== +==== c.ts (1 errors) ==== import { A } from "./b"; A; - ~ -!!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. -!!! related TS1377 a.ts:2:15: 'A' was exported here. A.displayName; - ~ -!!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. -!!! related TS1377 a.ts:2:15: 'A' was exported here. A(); ~ -!!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. -!!! related TS1377 a.ts:2:15: 'A' was exported here. - ~ !!! error TS2349: This expression is not callable. !!! error TS2349: Type 'typeof A' has no call signatures. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/typeOnlyMerge3.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/typeOnlyMerge3.errors.txt.diff new file mode 100644 index 000000000..cce54988e --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/typeOnlyMerge3.errors.txt.diff @@ -0,0 +1,32 @@ +--- old.typeOnlyMerge3.errors.txt ++++ new.typeOnlyMerge3.errors.txt +@@= skipped -0, +0 lines =@@ + b.ts(1,10): error TS2440: Import declaration conflicts with local declaration of 'A'. +-c.ts(2,1): error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. +-c.ts(3,1): error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. +-c.ts(4,1): error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. + c.ts(4,1): error TS2349: This expression is not callable. + Type 'typeof A' has no call signatures. + +@@= skipped -18, +15 lines =@@ + } + export { A }; + +-==== c.ts (4 errors) ==== ++==== c.ts (1 errors) ==== + import { A } from "./b"; + A; +- ~ +-!!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. +-!!! related TS1377 a.ts:2:15: 'A' was exported here. + A.displayName; +- ~ +-!!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. +-!!! related TS1377 a.ts:2:15: 'A' was exported here. + A(); +- ~ +-!!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. +-!!! related TS1377 a.ts:2:15: 'A' was exported here. + ~ + !!! error TS2349: This expression is not callable. + !!! error TS2349: Type 'typeof A' has no call signatures. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types b/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types index 73c994f78..95571b602 100644 --- a/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types +++ b/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types @@ -48,7 +48,7 @@ var t = p.x; === node_modules/math2d/index.d.ts === export as namespace Math2d; ->Math2d : typeof import("node_modules/math2d/index.d.ts") +>Math2d : typeof import("node_modules/math2d/index") export = M2D; >M2D : typeof import("node_modules/math2d/index.d.ts") diff --git a/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types.diff b/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types.diff index e3de594a2..c2336b4c0 100644 --- a/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types.diff +++ b/testdata/baselines/reference/submodule/conformance/umd-augmentation-3.types.diff @@ -12,7 +12,16 @@ var t = p.x; >t : number -@@= skipped -65, +65 lines =@@ +@@= skipped -12, +12 lines =@@ + + === node_modules/math2d/index.d.ts === + export as namespace Math2d; +->Math2d : typeof import("node_modules/math2d/index.d.ts") ++>Math2d : typeof import("node_modules/math2d/index") + + export = M2D; + >M2D : typeof import("node_modules/math2d/index.d.ts") +@@= skipped -53, +53 lines =@@ // Add a method to the class interface Vector { reverse(): Math2d.Point; diff --git a/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types b/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types index c49c82177..b500a26ac 100644 --- a/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types +++ b/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types @@ -46,7 +46,7 @@ var t = p.x; === node_modules/math2d/index.d.ts === export as namespace Math2d; ->Math2d : typeof import("node_modules/math2d/index.d.ts") +>Math2d : typeof import("node_modules/math2d/index") export = M2D; >M2D : typeof import("node_modules/math2d/index.d.ts") diff --git a/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types.diff b/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types.diff index ee8caf02b..7d963e8c0 100644 --- a/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types.diff +++ b/testdata/baselines/reference/submodule/conformance/umd-augmentation-4.types.diff @@ -1,6 +1,15 @@ --- old.umd-augmentation-4.types +++ new.umd-augmentation-4.types -@@= skipped -98, +98 lines =@@ +@@= skipped -45, +45 lines =@@ + + === node_modules/math2d/index.d.ts === + export as namespace Math2d; +->Math2d : typeof import("node_modules/math2d/index.d.ts") ++>Math2d : typeof import("node_modules/math2d/index") + + export = M2D; + >M2D : typeof import("node_modules/math2d/index.d.ts") +@@= skipped -53, +53 lines =@@ // Add a method to the class interface Vector { reverse(): Math2d.Point; diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js new file mode 100644 index 000000000..cf19f856c --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js @@ -0,0 +1,72 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/D.ts] *new* +export const x = 10; +//// [/home/src/workspaces/project/c.ts] *new* +import {x} from "./D" +//// [/home/src/workspaces/project/d.ts] *new* +export const y = 20; +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + "files": ["c.ts", "d.ts"] +} + +tsgo +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +c.ts:1:17 - error TS1261: Already included file name '/home/src/workspaces/project/D.ts' differs from file name '/home/src/workspaces/project/d.ts' only in casing. + The file is in the program because: + Imported via "./D" from file '/home/src/workspaces/project/c.ts' + Part of 'files' list in tsconfig.json + +1 import {x} from "./D" +   ~~~~~ + + tsconfig.json:2:23 - File is matched by 'files' list specified here. + 2 "files": ["c.ts", "d.ts"] +    ~~~~~~ + + +Found 1 error in c.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/D.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.x = void 0; +exports.x = 10; + +//// [/home/src/workspaces/project/c.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +//// [/home/src/workspaces/project/d.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.y = void 0; +exports.y = 20; + + diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js new file mode 100644 index 000000000..2c5f0bfa4 --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js @@ -0,0 +1,315 @@ +currentDirectory::/home/src/projects/project +useCaseSensitiveFileNames::false +Input:: +//// [/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts] *new* +export function foo(): void +//// [/home/src/projects/project/src/anotherFile.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; +//// [/home/src/projects/project/src/oneMore.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; +//// [/home/src/projects/project/src/struct.d.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; +//// [/home/src/projects/project/tsconfig.json] *new* +{} + +tsgo --explainFiles +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +src/Struct.d.ts:2:22 - error TS1149: File name '/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/home/src/projects/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/Struct.d.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +4 import * as xs4 from "./struct"; +   ~~~~~~~~~~ + + src/anotherFile.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/anotherFile.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +src/anotherFile.ts:2:22 - error TS1149: File name '/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/home/src/projects/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/anotherFile.ts:3:22 - error TS1261: Already included file name '/home/src/projects/project/src/Struct.d.ts' differs from file name '/home/src/projects/project/src/struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +3 import * as xs3 from "./Struct"; +   ~~~~~~~~~~ + + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/anotherFile.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +src/anotherFile.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +4 import * as xs4 from "./struct"; +   ~~~~~~~~~~ + + src/anotherFile.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +src/oneMore.ts:2:22 - error TS1149: File name '/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/home/src/projects/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/oneMore.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +4 import * as xs4 from "./struct"; +   ~~~~~~~~~~ + + src/anotherFile.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/anotherFile.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + +../../tslibs/TS/Lib/lib.d.ts + Default library for target 'ES5' +node_modules/fp-ts/lib/Struct.d.ts + Imported via "fp-ts/lib/Struct" from file 'src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file 'src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file 'src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file 'src/Struct.d.ts' + Imported via "fp-ts/lib/Struct" from file 'src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file 'src/oneMore.ts' +src/Struct.d.ts + Imported via "./Struct" from file 'src/anotherFile.ts' + Imported via "./Struct" from file 'src/Struct.d.ts' + Imported via "./struct" from file 'src/Struct.d.ts' + Imported via "./struct" from file 'src/anotherFile.ts' + Imported via "./Struct" from file 'src/oneMore.ts' + Imported via "./struct" from file 'src/oneMore.ts' + Matched by default include pattern '**/*' +src/anotherFile.ts + Matched by default include pattern '**/*' +src/oneMore.ts + Matched by default include pattern '**/*' + +Found 7 errors in 3 files. + +Errors Files + 2 src/Struct.d.ts:2 + 3 src/anotherFile.ts:2 + 2 src/oneMore.ts:2 + +//// [/home/src/projects/project/src/anotherFile.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +//// [/home/src/projects/project/src/oneMore.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js new file mode 100644 index 000000000..19ce089f6 --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js @@ -0,0 +1,75 @@ +currentDirectory::/user/username/projects/myproject +useCaseSensitiveFileNames::false +Input:: +//// [/user/username/projects/myproject/node_modules/fp-ts/lib/struct.d.ts] *new* +export function foo(): void +//// [/user/username/projects/myproject/src/struct.d.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; + +tsgo /user/username/projects/myproject/src/struct.d.ts --forceConsistentCasingInFileNames --explainFiles +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +src/struct.d.ts:2:22 - error TS1149: File name '/user/username/projects/myproject/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/user/username/projects/myproject/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/user/username/projects/myproject/src/struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/user/username/projects/myproject/src/struct.d.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/struct.d.ts:3:22 - error TS1149: File name '/user/username/projects/myproject/src/Struct.d.ts' differs from already included file name '/user/username/projects/myproject/src/struct.d.ts' only in casing. + The file is in the program because: + Root file specified for compilation + Imported via "./Struct" from file '/user/username/projects/myproject/src/struct.d.ts' + Imported via "./struct" from file '/user/username/projects/myproject/src/struct.d.ts' + +3 import * as xs3 from "./Struct"; +   ~~~~~~~~~~ + + src/struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +../../../../home/src/tslibs/TS/Lib/lib.d.ts + Default library for target 'ES5' +node_modules/fp-ts/lib/Struct.d.ts + Imported via "fp-ts/lib/Struct" from file 'src/struct.d.ts' + Imported via "fp-ts/lib/struct" from file 'src/struct.d.ts' +src/struct.d.ts + Root file specified for compilation + Imported via "./Struct" from file 'src/struct.d.ts' + Imported via "./struct" from file 'src/struct.d.ts' + +Found 2 errors in the same file, starting at: src/struct.d.ts:2 + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-triple-slash-ref-from-file.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-triple-slash-ref-from-file.js new file mode 100644 index 000000000..1d1692756 --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-triple-slash-ref-from-file.js @@ -0,0 +1,53 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::false +Input:: +//// [/home/src/workspaces/project/src/c.ts] *new* +/// +//// [/home/src/workspaces/project/src/d.ts] *new* +declare class c { } +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ } + +tsgo +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +src/c.ts:1:22 - error TS1261: Already included file name '/home/src/workspaces/project/src/D.ts' differs from file name '/home/src/workspaces/project/src/d.ts' only in casing. + The file is in the program because: + Referenced via './D.ts' from file '/home/src/workspaces/project/src/c.ts' + Matched by default include pattern '**/*' + +1 /// +   ~~~~~~ + + +Found 1 error in src/c.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/src/D.js] *new* + +//// [/home/src/workspaces/project/src/c.js] *new* +/// + + diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-type-ref-from-file.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-type-ref-from-file.js new file mode 100644 index 000000000..2f55514b0 --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-type-ref-from-file.js @@ -0,0 +1,58 @@ +currentDirectory::/user/username/projects/myproject +useCaseSensitiveFileNames::false +Input:: +//// [/user/username/projects/myproject/src/file2.d.ts] *new* +/// +declare const y: c; +//// [/user/username/projects/myproject/src/fileOne.d.ts] *new* +declare class c { } +//// [/user/username/projects/myproject/tsconfig.json] *new* +{ } + +tsgo -p /user/username/projects/myproject --explainFiles --traceResolution +ExitStatus:: Success +Output:: +======== Resolving type reference directive './fileOne.d.ts', containing file '/user/username/projects/myproject/src/file2.d.ts', root directory '/user/username/projects/myproject/node_modules/@types,/user/username/projects/node_modules/@types,/user/username/node_modules/@types,/user/node_modules/@types,/node_modules/@types'. ======== +Resolving with primary search path '/user/username/projects/myproject/node_modules/@types, /user/username/projects/node_modules/@types, /user/username/node_modules/@types, /user/node_modules/@types, /node_modules/@types'. +Directory '/user/username/projects/myproject/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/user/username/projects/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/user/username/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/user/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/node_modules/@types' does not exist, skipping all lookups in it. +Looking up in 'node_modules' folder, initial location '/user/username/projects/myproject/src'. +Loading module as file / folder, candidate module location '/user/username/projects/myproject/src/fileOne.d.ts', target file types: Declaration. +File name '/user/username/projects/myproject/src/fileOne.d.ts' has a '.d.ts' extension - stripping it. +File '/user/username/projects/myproject/src/fileOne.d.ts' exists - use it as a name resolution result. +Resolving real path for '/user/username/projects/myproject/src/fileOne.d.ts', result '/user/username/projects/myproject/src/fileOne.d.ts'. +======== Type reference directive './fileOne.d.ts' was successfully resolved to '/user/username/projects/myproject/src/fileOne.d.ts', primary: false. ======== +../../../../home/src/tslibs/TS/Lib/lib.d.ts + Default library for target 'ES5' +src/fileOne.d.ts + Type library referenced via './fileOne.d.ts' from file 'src/file2.d.ts' + Matched by default include pattern '**/*' +src/file2.d.ts + Matched by default include pattern '**/*' +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js b/testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js new file mode 100644 index 000000000..416b8b892 --- /dev/null +++ b/testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js @@ -0,0 +1,196 @@ +currentDirectory::/home/project +useCaseSensitiveFileNames::false +Input:: +//// [/home/node_modules/lib1/index.d.ts] *new* +import type { Foo } from 'someLib'; +export type { Foo as Foo1 }; +//// [/home/node_modules/lib1/package.json] *new* +{ + "name": "lib1" +} +//// [/home/node_modules/lib2/index.d.ts] *new* +import type { Foo } from 'somelib'; +export type { Foo as Foo2 }; +export declare const foo2: Foo; +//// [/home/node_modules/lib2/package.json] *new* +{ + "name": "lib2" +} +//// [/home/node_modules/otherLib/index.d.ts] *new* +export type Str = string; +//// [/home/node_modules/otherLib/package.json] *new* +{ + "name": "otherlib" +} +//// [/home/node_modules/someLib/index.d.ts] *new* +import type { Str } from 'otherLib'; +export type Foo = { foo: Str; }; +//// [/home/node_modules/someLib/package.json] *new* +{ + "name": "somelib" +} +//// [/home/project/src/index.ts] *new* +import type { Foo1 } from 'lib1'; +import type { Foo2 } from 'lib2'; +export const foo1: Foo1 = { foo: "a" }; +export const foo2: Foo2 = { foo: "b" }; +//// [/home/project/tsconfig.json] *new* +{ + "compilerOptions": { + "incremental": true + }, +} + +tsgo -p . +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +../node_modules/lib2/index.d.ts:1:26 - error TS1149: File name '/home/node_modules/somelib/index.d.ts' differs from already included file name '/home/node_modules/someLib/index.d.ts' only in casing. + The file is in the program because: + Imported via 'someLib' from file '/home/node_modules/lib1/index.d.ts' + Imported via 'somelib' from file '/home/node_modules/lib2/index.d.ts' + +1 import type { Foo } from 'somelib'; +   ~~~~~~~~~ + + ../node_modules/lib1/index.d.ts:1:26 - File is included via import here. + 1 import type { Foo } from 'someLib'; +    ~~~~~~~~~ + + +Found 1 error in ../node_modules/lib2/index.d.ts:1 + +//// [/home/project/src/index.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.foo2 = exports.foo1 = void 0; +exports.foo1 = { foo: "a" }; +exports.foo2 = { foo: "b" }; + +//// [/home/project/tsconfig.tsbuildinfo] *new* +{"version":"FakeTSVersion","errors":true,"root":[6],"fileNames":["lib.d.ts","../node_modules/otherlib/index.d.ts","../node_modules/somelib/index.d.ts","../node_modules/lib1/index.d.ts","../node_modules/lib2/index.d.ts","./src/index.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"1fe659ed0634bb57b6dc25e9062f1162-export type Str = string;","12e112ff6e2744bb42d8e0b511e44117-import type { Str } from 'otherLib';\nexport type Foo = { foo: Str; };","b6305455d920a6729c435e6acf45eff6-import type { Foo } from 'someLib';\nexport type { Foo as Foo1 };","a5393e550a9c20a242a120bf6410db48-import type { Foo } from 'somelib';\nexport type { Foo as Foo2 };\nexport declare const foo2: Foo;","42aef197ff5f079223e2c29fb2e77cc5-import type { Foo1 } from 'lib1';\nimport type { Foo2 } from 'lib2';\nexport const foo1: Foo1 = { foo: \"a\" };\nexport const foo2: Foo2 = { foo: \"b\" };"],"fileIdsList":[[3],[2],[4,5]],"referencedMap":[[4,1],[5,1],[3,2],[6,3]]} +//// [/home/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new* +{ + "version": "FakeTSVersion", + "errors": true, + "root": [ + { + "files": [ + "./src/index.ts" + ], + "original": 6 + } + ], + "fileNames": [ + "lib.d.ts", + "../node_modules/otherlib/index.d.ts", + "../node_modules/somelib/index.d.ts", + "../node_modules/lib1/index.d.ts", + "../node_modules/lib2/index.d.ts", + "./src/index.ts" + ], + "fileInfos": [ + { + "fileName": "lib.d.ts", + "version": "8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", + "signature": "8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true, + "impliedNodeFormat": "CommonJS", + "original": { + "version": "8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true, + "impliedNodeFormat": 1 + } + }, + { + "fileName": "../node_modules/otherlib/index.d.ts", + "version": "1fe659ed0634bb57b6dc25e9062f1162-export type Str = string;", + "signature": "1fe659ed0634bb57b6dc25e9062f1162-export type Str = string;", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "../node_modules/somelib/index.d.ts", + "version": "12e112ff6e2744bb42d8e0b511e44117-import type { Str } from 'otherLib';\nexport type Foo = { foo: Str; };", + "signature": "12e112ff6e2744bb42d8e0b511e44117-import type { Str } from 'otherLib';\nexport type Foo = { foo: Str; };", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "../node_modules/lib1/index.d.ts", + "version": "b6305455d920a6729c435e6acf45eff6-import type { Foo } from 'someLib';\nexport type { Foo as Foo1 };", + "signature": "b6305455d920a6729c435e6acf45eff6-import type { Foo } from 'someLib';\nexport type { Foo as Foo1 };", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "../node_modules/lib2/index.d.ts", + "version": "a5393e550a9c20a242a120bf6410db48-import type { Foo } from 'somelib';\nexport type { Foo as Foo2 };\nexport declare const foo2: Foo;", + "signature": "a5393e550a9c20a242a120bf6410db48-import type { Foo } from 'somelib';\nexport type { Foo as Foo2 };\nexport declare const foo2: Foo;", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "./src/index.ts", + "version": "42aef197ff5f079223e2c29fb2e77cc5-import type { Foo1 } from 'lib1';\nimport type { Foo2 } from 'lib2';\nexport const foo1: Foo1 = { foo: \"a\" };\nexport const foo2: Foo2 = { foo: \"b\" };", + "signature": "42aef197ff5f079223e2c29fb2e77cc5-import type { Foo1 } from 'lib1';\nimport type { Foo2 } from 'lib2';\nexport const foo1: Foo1 = { foo: \"a\" };\nexport const foo2: Foo2 = { foo: \"b\" };", + "impliedNodeFormat": "CommonJS" + } + ], + "fileIdsList": [ + [ + "../node_modules/somelib/index.d.ts" + ], + [ + "../node_modules/otherlib/index.d.ts" + ], + [ + "../node_modules/lib1/index.d.ts", + "../node_modules/lib2/index.d.ts" + ] + ], + "referencedMap": { + "../node_modules/lib1/index.d.ts": [ + "../node_modules/somelib/index.d.ts" + ], + "../node_modules/lib2/index.d.ts": [ + "../node_modules/somelib/index.d.ts" + ], + "../node_modules/somelib/index.d.ts": [ + "../node_modules/otherlib/index.d.ts" + ], + "./src/index.ts": [ + "../node_modules/lib1/index.d.ts", + "../node_modules/lib2/index.d.ts" + ] + }, + "size": 1685 +} +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/tslibs/TS/Lib/lib.d.ts +*refresh* /home/node_modules/otherLib/index.d.ts +*refresh* /home/node_modules/someLib/index.d.ts +*refresh* /home/node_modules/lib1/index.d.ts +*refresh* /home/node_modules/lib2/index.d.ts +*refresh* /home/project/src/index.ts +Signatures::