326 lines
15 KiB
Go
326 lines
15 KiB
Go
package ls
|
|
|
|
import (
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
)
|
|
|
|
type Import struct {
|
|
name string
|
|
kind ImportKind // ImportKindCommonJS | ImportKindNamespace
|
|
addAsTypeOnly AddAsTypeOnly
|
|
propertyName string // Use when needing to generate an `ImportSpecifier with a `propertyName`; the name preceding "as" keyword (propertyName = "" when "as" is absent)
|
|
}
|
|
|
|
func (ct *changeTracker) addNamespaceQualifier(sourceFile *ast.SourceFile, qualification *Qualification) {
|
|
ct.insertText(sourceFile, qualification.usagePosition, qualification.namespacePrefix+".")
|
|
}
|
|
|
|
func (ct *changeTracker) doAddExistingFix(
|
|
sourceFile *ast.SourceFile,
|
|
clause *ast.Node, // ImportClause | ObjectBindingPattern,
|
|
defaultImport *Import,
|
|
namedImports []*Import,
|
|
// removeExistingImportSpecifiers *core.Set[ImportSpecifier | BindingElement] // !!! remove imports not implemented
|
|
preferences *UserPreferences,
|
|
) {
|
|
switch clause.Kind {
|
|
case ast.KindObjectBindingPattern:
|
|
if clause.Kind == ast.KindObjectBindingPattern {
|
|
// bindingPattern := clause.AsBindingPattern()
|
|
// !!! adding *and* removing imports not implemented
|
|
// if (removeExistingImportSpecifiers && core.Some(bindingPattern.Elements, func(e *ast.Node) bool {
|
|
// return removeExistingImportSpecifiers.Has(e)
|
|
// })) {
|
|
// If we're both adding and removing elements, just replace and reprint the whole
|
|
// node. The change tracker doesn't understand all the operations and can insert or
|
|
// leave behind stray commas.
|
|
// ct.replaceNode(
|
|
// sourceFile,
|
|
// bindingPattern,
|
|
// ct.NodeFactory.NewObjectBindingPattern([
|
|
// ...bindingPattern.Elements.Filter(func(e *ast.Node) bool {
|
|
// return !removeExistingImportSpecifiers.Has(e)
|
|
// }),
|
|
// ...defaultImport ? [ct.NodeFactory.createBindingElement(/*dotDotDotToken*/ nil, /*propertyName*/ "default", defaultImport.name)] : emptyArray,
|
|
// ...namedImports.map(i => ct.NodeFactory.createBindingElement(/*dotDotDotToken*/ nil, i.propertyName, i.name)),
|
|
// ]),
|
|
// )
|
|
// return
|
|
// }
|
|
if defaultImport != nil {
|
|
ct.addElementToBindingPattern(sourceFile, clause, defaultImport.name, ptrTo("default"))
|
|
}
|
|
for _, specifier := range namedImports {
|
|
ct.addElementToBindingPattern(sourceFile, clause, specifier.name, &specifier.propertyName)
|
|
}
|
|
return
|
|
}
|
|
case ast.KindImportClause:
|
|
|
|
importClause := clause.AsImportClause()
|
|
|
|
// promoteFromTypeOnly = true if we need to promote the entire original clause from type only
|
|
promoteFromTypeOnly := importClause.IsTypeOnly() && core.Some(append(namedImports, defaultImport), func(i *Import) bool {
|
|
if i == nil {
|
|
return false
|
|
}
|
|
return i.addAsTypeOnly == AddAsTypeOnlyNotAllowed
|
|
})
|
|
|
|
existingSpecifiers := []*ast.Node{} // []*ast.ImportSpecifier
|
|
if importClause.NamedBindings != nil && importClause.NamedBindings.Kind == ast.KindNamedImports {
|
|
existingSpecifiers = importClause.NamedBindings.Elements()
|
|
}
|
|
|
|
if defaultImport != nil {
|
|
debug.Assert(clause.Name() == nil, "Cannot add a default import to an import clause that already has one")
|
|
ct.insertNodeAt(sourceFile, core.TextPos(astnav.GetStartOfNode(clause, sourceFile, false)), ct.NodeFactory.NewIdentifier(defaultImport.name), changeNodeOptions{suffix: ", "})
|
|
}
|
|
|
|
if len(namedImports) > 0 {
|
|
// !!! OrganizeImports not yet implemented
|
|
// specifierComparer, isSorted := OrganizeImports.getNamedImportSpecifierComparerWithDetection(importClause.Parent, preferences, sourceFile);
|
|
newSpecifiers := core.Map(namedImports, func(namedImport *Import) *ast.Node {
|
|
var identifier *ast.Node
|
|
if namedImport.propertyName != "" {
|
|
identifier = ct.NodeFactory.NewIdentifier(namedImport.propertyName).AsIdentifier().AsNode()
|
|
}
|
|
return ct.NodeFactory.NewImportSpecifier(
|
|
(!importClause.IsTypeOnly() || promoteFromTypeOnly) && shouldUseTypeOnly(namedImport.addAsTypeOnly, preferences),
|
|
identifier,
|
|
ct.NodeFactory.NewIdentifier(namedImport.name),
|
|
)
|
|
}) // !!! sort with specifierComparer
|
|
|
|
// !!! remove imports not implemented
|
|
// if (removeExistingImportSpecifiers) {
|
|
// // If we're both adding and removing specifiers, just replace and reprint the whole
|
|
// // node. The change tracker doesn't understand all the operations and can insert or
|
|
// // leave behind stray commas.
|
|
// ct.replaceNode(
|
|
// sourceFile,
|
|
// importClause.NamedBindings,
|
|
// ct.NodeFactory.updateNamedImports(
|
|
// importClause.NamedBindings.AsNamedImports(),
|
|
// append(core.Filter(existingSpecifiers, func (s *ast.ImportSpecifier) bool {return !removeExistingImportSpecifiers.Has(s)}), newSpecifiers...), // !!! sort with specifierComparer
|
|
// ),
|
|
// );
|
|
// } else if (len(existingSpecifiers) > 0 && isSorted != false) {
|
|
// !!! OrganizeImports not implemented
|
|
// The sorting preference computed earlier may or may not have validated that these particular
|
|
// import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return
|
|
// nonsense. So if there are existing specifiers, even if we know the sorting preference, we
|
|
// need to ensure that the existing specifiers are sorted according to the preference in order
|
|
// to do a sorted insertion.
|
|
// changed to check if existing specifiers are sorted
|
|
// if we're promoting the clause from type-only, we need to transform the existing imports before attempting to insert the new named imports
|
|
// transformedExistingSpecifiers := existingSpecifiers
|
|
// if promoteFromTypeOnly && existingSpecifiers {
|
|
// transformedExistingSpecifiers = ct.NodeFactory.updateNamedImports(
|
|
// importClause.NamedBindings.AsNamedImports(),
|
|
// core.SameMap(existingSpecifiers, func(e *ast.ImportSpecifier) *ast.ImportSpecifier {
|
|
// return ct.NodeFactory.updateImportSpecifier(e, /*isTypeOnly*/ true, e.propertyName, e.name)
|
|
// }),
|
|
// ).elements
|
|
// }
|
|
// for _, spec := range newSpecifiers {
|
|
// insertionIndex := OrganizeImports.getImportSpecifierInsertionIndex(transformedExistingSpecifiers, spec, specifierComparer);
|
|
// ct.insertImportSpecifierAtIndex(sourceFile, spec, importClause.namedBindings as NamedImports, insertionIndex);
|
|
// }
|
|
// } else
|
|
if len(existingSpecifiers) > 0 {
|
|
for _, spec := range newSpecifiers {
|
|
ct.insertNodeInListAfter(sourceFile, existingSpecifiers[len(existingSpecifiers)-1], spec.AsNode(), existingSpecifiers)
|
|
}
|
|
} else {
|
|
if len(newSpecifiers) > 0 {
|
|
namedImports := ct.NodeFactory.NewNamedImports(ct.NodeFactory.NewNodeList(newSpecifiers))
|
|
if importClause.NamedBindings != nil {
|
|
ct.replaceNode(sourceFile, importClause.NamedBindings, namedImports, nil)
|
|
} else {
|
|
if clause.Name() == nil {
|
|
panic("Import clause must have either named imports or a default import")
|
|
}
|
|
ct.insertNodeAfter(sourceFile, clause.Name(), namedImports)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if promoteFromTypeOnly {
|
|
// !!! promote type-only imports not implemented
|
|
|
|
// ct.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile));
|
|
// if (existingSpecifiers) {
|
|
// // We used to convert existing specifiers to type-only only if compiler options indicated that
|
|
// // would be meaningful (see the `importNameElisionDisabled` utility function), but user
|
|
// // feedback indicated a preference for preserving the type-onlyness of existing specifiers
|
|
// // regardless of whether it would make a difference in emit.
|
|
// for _, specifier := range existingSpecifiers {
|
|
// ct.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, specifier);
|
|
// }
|
|
// }
|
|
}
|
|
default:
|
|
panic("Unsupported clause kind: " + clause.Kind.String() + "for doAddExistingFix")
|
|
}
|
|
}
|
|
|
|
func (ct *changeTracker) addElementToBindingPattern(sourceFile *ast.SourceFile, bindingPattern *ast.Node, name string, propertyName *string) {
|
|
element := ct.newBindingElementFromNameAndPropertyName(name, propertyName)
|
|
if len(bindingPattern.Elements()) > 0 {
|
|
ct.insertNodeInListAfter(sourceFile, bindingPattern.Elements()[len(bindingPattern.Elements())-1], element, nil)
|
|
} else {
|
|
ct.replaceNode(sourceFile, bindingPattern, ct.NodeFactory.NewBindingPattern(
|
|
ast.KindObjectBindingPattern,
|
|
ct.NodeFactory.NewNodeList([]*ast.Node{element}),
|
|
), nil)
|
|
}
|
|
}
|
|
|
|
func (ct *changeTracker) newBindingElementFromNameAndPropertyName(name string, propertyName *string) *ast.Node {
|
|
var newPropertyNameIdentifier *ast.Node
|
|
if propertyName != nil {
|
|
newPropertyNameIdentifier = ct.NodeFactory.NewIdentifier(*propertyName)
|
|
}
|
|
return ct.NodeFactory.NewBindingElement(
|
|
nil, /*dotDotDotToken*/
|
|
newPropertyNameIdentifier,
|
|
ct.NodeFactory.NewIdentifier(name),
|
|
nil, /* initializer */
|
|
)
|
|
}
|
|
|
|
func (ct *changeTracker) insertImports(sourceFile *ast.SourceFile, imports []*ast.Statement, blankLineBetween bool, preferences *UserPreferences) {
|
|
var existingImportStatements []*ast.Statement
|
|
|
|
if imports[0].Kind == ast.KindVariableStatement {
|
|
existingImportStatements = core.Filter(sourceFile.Statements.Nodes, ast.IsRequireVariableStatement)
|
|
} else {
|
|
existingImportStatements = core.Filter(sourceFile.Statements.Nodes, ast.IsAnyImportSyntax)
|
|
}
|
|
// !!! OrganizeImports
|
|
// { comparer, isSorted } := OrganizeImports.getOrganizeImportsStringComparerWithDetection(existingImportStatements, preferences);
|
|
// sortedNewImports := isArray(imports) ? toSorted(imports, (a, b) => OrganizeImports.compareImportsOrRequireStatements(a, b, comparer)) : [imports];
|
|
sortedNewImports := imports
|
|
// !!! FutureSourceFile
|
|
// if !isFullSourceFile(sourceFile) {
|
|
// for _, newImport := range sortedNewImports {
|
|
// // Insert one at a time to send correct original source file for accurate text reuse
|
|
// // when some imports are cloned from existing ones in other files.
|
|
// ct.insertStatementsInNewFile(sourceFile.fileName, []*ast.Node{newImport}, ast.GetSourceFileOfNode(getOriginalNode(newImport)))
|
|
// }
|
|
// return;
|
|
// }
|
|
|
|
// if len(existingImportStatements) > 0 && isSorted {
|
|
// for _, newImport := range sortedNewImports {
|
|
// insertionIndex := OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport, comparer)
|
|
// if insertionIndex == 0 {
|
|
// // If the first import is top-of-file, insert after the leading comment which is likely the header.
|
|
// options := existingImportStatements[0] == sourceFile.statements[0] ? { leadingTriviaOption: textchanges.LeadingTriviaOption.Exclude } : {};
|
|
// ct.insertNodeBefore(sourceFile, existingImportStatements[0], newImport, /*blankLineBetween*/ false, options);
|
|
// } else {
|
|
// prevImport := existingImportStatements[insertionIndex - 1]
|
|
// ct.insertNodeAfter(sourceFile, prevImport, newImport);
|
|
// }
|
|
// }
|
|
// return
|
|
// }
|
|
|
|
if len(existingImportStatements) > 0 {
|
|
ct.insertNodesAfter(sourceFile, existingImportStatements[len(existingImportStatements)-1], sortedNewImports)
|
|
} else {
|
|
ct.insertAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween)
|
|
}
|
|
}
|
|
|
|
func (ct *changeTracker) makeImport(defaultImport *ast.IdentifierNode, namedImports []*ast.Node, moduleSpecifier *ast.Expression, isTypeOnly bool) *ast.Statement {
|
|
var newNamedImports *ast.Node
|
|
if len(namedImports) > 0 {
|
|
newNamedImports = ct.NodeFactory.NewNamedImports(ct.NodeFactory.NewNodeList(namedImports))
|
|
}
|
|
var importClause *ast.Node
|
|
if defaultImport != nil || newNamedImports != nil {
|
|
importClause = ct.NodeFactory.NewImportClause(core.IfElse(isTypeOnly, ast.KindTypeKeyword, ast.KindUnknown), defaultImport, newNamedImports)
|
|
}
|
|
return ct.NodeFactory.NewImportDeclaration( /*modifiers*/ nil, importClause, moduleSpecifier, nil /*attributes*/)
|
|
}
|
|
|
|
func (ct *changeTracker) getNewImports(
|
|
moduleSpecifier string,
|
|
// quotePreference quotePreference, // !!! quotePreference
|
|
defaultImport *Import,
|
|
namedImports []*Import,
|
|
namespaceLikeImport *Import, // { importKind: ImportKind.CommonJS | ImportKind.Namespace; }
|
|
compilerOptions *core.CompilerOptions,
|
|
preferences *UserPreferences,
|
|
) []*ast.Statement {
|
|
moduleSpecifierStringLiteral := ct.NodeFactory.NewStringLiteral(moduleSpecifier)
|
|
var statements []*ast.Statement // []AnyImportSyntax
|
|
if defaultImport != nil || len(namedImports) > 0 {
|
|
// `verbatimModuleSyntax` should prefer top-level `import type` -
|
|
// even though it's not an error, it would add unnecessary runtime emit.
|
|
topLevelTypeOnly := (defaultImport == nil || needsTypeOnly(defaultImport.addAsTypeOnly)) &&
|
|
core.Every(namedImports, func(i *Import) bool { return needsTypeOnly(i.addAsTypeOnly) }) ||
|
|
(compilerOptions.VerbatimModuleSyntax.IsTrue() || preferences.PreferTypeOnlyAutoImports) &&
|
|
defaultImport != nil && defaultImport.addAsTypeOnly != AddAsTypeOnlyNotAllowed && !core.Some(namedImports, func(i *Import) bool { return i.addAsTypeOnly == AddAsTypeOnlyNotAllowed })
|
|
|
|
var defaultImportNode *ast.Node
|
|
if defaultImport != nil {
|
|
defaultImportNode = ct.NodeFactory.NewIdentifier(defaultImport.name)
|
|
}
|
|
|
|
statements = append(statements, ct.makeImport(defaultImportNode, core.Map(namedImports, func(namedImport *Import) *ast.Node {
|
|
var namedImportPropertyName *ast.Node
|
|
if namedImport.propertyName != "" {
|
|
namedImportPropertyName = ct.NodeFactory.NewIdentifier(namedImport.propertyName)
|
|
}
|
|
return ct.NodeFactory.NewImportSpecifier(
|
|
!topLevelTypeOnly && shouldUseTypeOnly(namedImport.addAsTypeOnly, preferences),
|
|
namedImportPropertyName,
|
|
ct.NodeFactory.NewIdentifier(namedImport.name),
|
|
)
|
|
}), moduleSpecifierStringLiteral, topLevelTypeOnly))
|
|
}
|
|
|
|
if namespaceLikeImport != nil {
|
|
var declaration *ast.Statement
|
|
if namespaceLikeImport.kind == ImportKindCommonJS {
|
|
declaration = ct.NodeFactory.NewImportEqualsDeclaration(
|
|
/*modifiers*/ nil,
|
|
shouldUseTypeOnly(namespaceLikeImport.addAsTypeOnly, preferences),
|
|
ct.NodeFactory.NewIdentifier(namespaceLikeImport.name),
|
|
ct.NodeFactory.NewExternalModuleReference(moduleSpecifierStringLiteral),
|
|
)
|
|
} else {
|
|
declaration = ct.NodeFactory.NewImportDeclaration(
|
|
/*modifiers*/ nil,
|
|
ct.NodeFactory.NewImportClause(
|
|
/*phaseModifier*/ core.IfElse(shouldUseTypeOnly(namespaceLikeImport.addAsTypeOnly, preferences), ast.KindTypeKeyword, ast.KindUnknown),
|
|
/*name*/ nil,
|
|
ct.NodeFactory.NewNamespaceImport(ct.NodeFactory.NewIdentifier(namespaceLikeImport.name)),
|
|
),
|
|
moduleSpecifierStringLiteral,
|
|
/*attributes*/ nil,
|
|
)
|
|
}
|
|
statements = append(statements, declaration)
|
|
}
|
|
if len(statements) == 0 {
|
|
panic("No statements to insert for new imports")
|
|
}
|
|
return statements
|
|
}
|
|
|
|
func needsTypeOnly(addAsTypeOnly AddAsTypeOnly) bool {
|
|
return addAsTypeOnly == AddAsTypeOnlyRequired
|
|
}
|
|
|
|
func shouldUseTypeOnly(addAsTypeOnly AddAsTypeOnly, preferences *UserPreferences) bool {
|
|
return needsTypeOnly(addAsTypeOnly) || addAsTypeOnly != AddAsTypeOnlyNotAllowed && preferences.PreferTypeOnlyAutoImports
|
|
}
|