Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import more modules #488

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions cli/src/main/scala/org/scalablytyped/converter/cli/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,19 @@ object Main {
}

val DefaultOptions = ConversionOptions(
useScalaJsDomTypes = true,
outputPackage = Name.typings,
enableScalaJsDefined = Selection.All,
flavour = Flavour.Normal,
ignored = SortedSet("typescript"),
stdLibs = SortedSet("es6"),
expandTypeMappings = EnabledTypeMappingExpansion.DefaultSelection,
versions = Versions(Versions.Scala3, Versions.ScalaJs1),
organization = "org.scalablytyped",
enableReactTreeShaking = Selection.None,
enableLongApplyMethod = false,
privateWithin = None,
useScalaJsDomTypes = true,
outputPackage = Name.typings,
enableScalaJsDefined = Selection.All,
flavour = Flavour.Normal,
ignored = SortedSet("typescript"),
stdLibs = SortedSet("es6"),
expandTypeMappings = EnabledTypeMappingExpansion.DefaultSelection,
versions = Versions(Versions.Scala3, Versions.ScalaJs1),
organization = "org.scalablytyped",
enableReactTreeShaking = Selection.None,
enableLongApplyMethod = false,
privateWithin = None,
useDeprecatedModuleNames = false,
)

case class Config(
Expand Down Expand Up @@ -166,6 +167,11 @@ object Main {
opt[Boolean]("enableLongApplyMethod")
.action((x, c) => c.mapConversion(_.copy(enableLongApplyMethod = x)))
.text(s"Enables long apply methods, instead of implicit ops builders"),
opt[Boolean]("shortModuleNames")
.action((x, c) => c.mapConversion(_.copy(useDeprecatedModuleNames = x)))
.text(
s"Enables short module names. This used to be the default, and is now deprecated since it's so difficult to navigate",
),
arg[Seq[TsIdentLibrary]]("libs")
.text("Libraries you want to convert from node_modules")
.unbounded()
Expand Down Expand Up @@ -263,11 +269,12 @@ object Main {
)
.next(
new Phase2ToScalaJs(
pedantic = false,
scalaVersion = conversion.versions.scala,
enableScalaJsDefined = conversion.enableScalaJsDefined,
outputPkg = conversion.outputPackage,
flavour = conversion.flavourImpl,
pedantic = false,
scalaVersion = conversion.versions.scala,
enableScalaJsDefined = conversion.enableScalaJsDefined,
outputPkg = conversion.outputPackage,
flavour = conversion.flavourImpl,
useDeprecatedModuleNames = conversion.useDeprecatedModuleNames,
),
"scala.js",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ object Marker {
case object HasClassParent extends Marker

case class NameHint(value: String) extends Marker
case class ModuleAliases(aliases: IArray[TsIdentModule]) extends Marker
case class WasLiteral(lit: ExprTree.Lit) extends Marker
case class WasUnion(related: IArray[TypeRef]) extends Marker

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ final class AdaptiveNamingImport(private val rewrites: Map[IArray[TsIdent], Qual

object AdaptiveNamingImport {
def apply(
outputPkg: Name,
libraryName: TsIdentLibrary,
library: TsParsedFile,
depsRewrites: IArray[AdaptiveNamingImport],
cleanIllegalNames: CleanIllegalNames,
outputPkg: Name,
libraryName: TsIdentLibrary,
library: TsParsedFile,
depsRewrites: IArray[AdaptiveNamingImport],
cleanIllegalNames: CleanIllegalNames,
useDeprecatedModuleNames: Boolean,
): AdaptiveNamingImport = {
val allReferences: IArray[IArray[TsIdent]] =
TsTreeTraverse
Expand Down Expand Up @@ -60,9 +61,15 @@ object AdaptiveNamingImport {
case IArray.Empty => ()
case whole @ IArray.initLast(parent, current) =>
val parentTranslated = registeredReferences(parent)
val variants = variantsFor(current, parent.exists(_.isInstanceOf[TsIdentModule]), illegalNames)
var continue = true
val iter = variants.iterator
val variants = variantsFor(
current,
parent.exists(_.isInstanceOf[TsIdentModule]),
parent.collectFirst { case x: TsIdentLibrary => x },
illegalNames,
useDeprecatedModuleNames,
)
var continue = true
val iter = variants.iterator
while (continue && iter.hasNext) {
val currentVariant = iter.next()
val possibleQname = QualifiedName(parentTranslated.parts :+ Name.necessaryRewrite(Name(currentVariant)))
Expand All @@ -88,7 +95,13 @@ object AdaptiveNamingImport {
new AdaptiveNamingImport(registeredReferences.toMap)
}

def variantsFor(tsIdent: TsIdent, hasModuleParent: Boolean, illegalNames: Set[String]): Stream[String] = {
def variantsFor(
tsIdent: TsIdent,
hasModuleParent: Boolean,
inLib: Option[TsIdentLibrary],
illegalNames: Set[String],
useDeprecatedModuleNames: Boolean,
): Stream[String] = {
val base = tsIdent match {
case TsIdent.namespaced => Stream(Name.namespaced.unescaped)
case TsIdent.Apply => Stream(Name.APPLY.unescaped)
Expand All @@ -100,7 +113,7 @@ object AdaptiveNamingImport {
/* todo: We should look up what the augmented module is called and reuse it. I don't care enough to do it now */
nameVariants(joinCamelCase(m.scopeOpt.toList ++ m.fragments)).map(_ + "AugmentingMod")

case m: TsIdentModule =>
case m: TsIdentModule if useDeprecatedModuleNames =>
val increasingLength: Stream[List[String]] = {
val (libraryBits, moduleBits) =
m match {
Expand All @@ -122,15 +135,28 @@ object AdaptiveNamingImport {

preferCamelCase.flatMap(frags => nameVariants(addMod(joinCamelCase(frags))))

case library: TsIdentLibrary =>
nameVariants(toCamelCase(library.value))
case m: TsIdentModule =>
val shortenedFragments = {
inLib match {
case Some(TsIdentLibrarySimple(value)) if m.fragments.head == value =>
m.fragments.drop(1)
case Some(TsIdentLibraryScoped(scope, name)) if m.scopeOpt.contains(scope) && m.fragments.head == name =>
m.fragments.drop(1)
case _ =>
m.fragments
}
}
Stream(addMod(joinCamelCase(shortenedFragments.map(toCamelCase))))

case _: TsIdentImport => sys.error("unexpected")
case library: TsIdentLibrary => variantsForLibName(library)
case _: TsIdentImport => sys.error("unexpected")
}

base #::: base.map(_ + "_") #::: base.map(_ + "__")
}

def variantsForLibName(library: TsIdentLibrary) =
nameVariants(toCamelCase(library.value))
private def addMod(str: String) = str match {
case "" => Name.mod.unescaped
case nonEmpty => nonEmpty + "Mod"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import org.scalablytyped.converter.internal.ts.TsIdentLibrary
import scala.collection.immutable.SortedSet

case class ConversionOptions(
useScalaJsDomTypes: Boolean,
flavour: Flavour,
outputPackage: Name,
enableScalaJsDefined: Selection[TsIdentLibrary],
stdLibs: SortedSet[String],
expandTypeMappings: Selection[TsIdentLibrary],
ignored: SortedSet[String],
versions: Versions,
organization: String,
enableReactTreeShaking: Selection[Name],
enableLongApplyMethod: Boolean,
privateWithin: Option[Name],
useScalaJsDomTypes: Boolean,
flavour: Flavour,
outputPackage: Name,
enableScalaJsDefined: Selection[TsIdentLibrary],
stdLibs: SortedSet[String],
expandTypeMappings: Selection[TsIdentLibrary],
ignored: SortedSet[String],
versions: Versions,
organization: String,
enableReactTreeShaking: Selection[Name],
enableLongApplyMethod: Boolean,
privateWithin: Option[Name],
useDeprecatedModuleNames: Boolean,
) {
val ignoredLibs: Set[TsIdentLibrary] =
ignored.map(TsIdentLibrary.apply)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object ImportName {
Name.necessaryRewrite(Name(i.value))

def apply(i: TsIdentLibrary): Name =
Name.necessaryRewrite(Name(AdaptiveNamingImport.variantsFor(i, hasModuleParent = false, Set()).head))
Name.necessaryRewrite(Name(AdaptiveNamingImport.variantsForLibName(i).head))

object withJsNameAnnotation {
def apply(original: TsIdentSimple): (Name, Option[Annotation.JsName]) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ sealed trait LibTsSource extends TsTreeScope.TsLib {

lazy val tsConfig: Option[TsConfig] =
Json.opt[TsConfig](folder.path / "tsconfig.json")

lazy val shortenedFiles: IArray[InFile] = LibTsSource.findShortenedFiles(this)
}

object LibTsSource {
def hasTypescriptSources(folder: InFolder): Boolean =
os.walk.stream(folder.path, _.last == "node_modules").exists(_.last.endsWith(".d.ts"))
os.walk.stream(folder.path, skip = _.last == "node_modules").exists(_.last.endsWith(".d.ts"))

final case class StdLibSource(folder: InFolder, files: IArray[InFile], libName: TsIdentLibrary) extends LibTsSource

Expand All @@ -34,48 +32,4 @@ object LibTsSource {
Ordering.by[S, String](_.pathString)
implicit val SourceFormatter: Formatter[LibTsSource] =
_.libName.value

/* for files referenced through here we must shorten the paths */
def findShortenedFiles(src: LibTsSource): IArray[InFile] = {
def fromTypingsJson(fromFolder: LibTsSource.FromFolder, files: Option[IArray[String]]): IArray[InFile] =
files.getOrElse(IArray.Empty).collect {
case path if path.endsWith("typings.json") =>
val typingsJsonPath = fromFolder.folder.path / os.RelPath(path)
val typingsJson = Json.force[TypingsJson](typingsJsonPath)
InFile(typingsJsonPath / os.up / typingsJson.main)
}

def fromFileEntry(fromFolder: LibTsSource.FromFolder, files: Option[IArray[String]]): IArray[InFile] =
files.getOrElse(IArray.Empty).mapNotNone(file => LibraryResolver.file(fromFolder.folder, file))

def fromModuleDeclaration(
fromFolder: LibTsSource.FromFolder,
files: Option[Map[String, String]],
): IArray[InFile] = {
val files1 = files match {
case Some(files) => IArray.fromTraversable(files.values)
case None => IArray.Empty
}

files1
.mapNotNone(file => LibraryResolver.file(fromFolder.folder, file))
.mapNotNone {
case existingFile if LibTsSource.hasTypescriptSources(existingFile.folder) => Some(existingFile)
case _ => None
}
}

src match {
case _: StdLibSource => Empty
case f: FromFolder =>
val fromTypings =
IArray(
fromFileEntry(f, f.packageJsonOpt.flatMap(_.parsedTypes).orElse(f.packageJsonOpt.flatMap(_.parsedTypings))),
fromTypingsJson(f, f.packageJsonOpt.flatMap(_.parsedTypings)),
).flatten

if (fromTypings.nonEmpty) fromTypings
else fromModuleDeclaration(f, f.packageJsonOpt.flatMap(_.parsedModules))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class LibraryResolver(
value match {
case LocalPath(localPath) =>
file(folder, localPath).map { inFile =>
ResolvedModule.Local(inFile, LibraryResolver.moduleNameFor(source, inFile).head)
ResolvedModule.Local(inFile, LibraryResolver.moduleNameFor(source, inFile))
}
case globalRef =>
val modName = ModuleNameParser(globalRef.split("/").to[List], keepIndexFragment = true)
Expand Down Expand Up @@ -56,44 +56,16 @@ object LibraryResolver {
case class Ignored(name: TsIdentLibrary) extends Res[Nothing]
case class NotAvailable(name: TsIdentLibrary) extends Res[Nothing]

def moduleNameFor(source: LibTsSource, file: InFile): IArray[TsIdentModule] = {
val shortened: Option[TsIdentModule] =
if (source.shortenedFiles.contains(file)) {
source.libName match {
case TsIdentLibraryScoped(scope, name) =>
Some(TsIdentModule(Some(scope), List(name)))
case TsIdentLibrarySimple(value) =>
Some(TsIdentModule(None, value :: Nil))
}
} else None

val longName: TsIdentModule = {
val keepIndexPath = file.path match {
case base / segment / "index.d.ts" => files.exists(base / segment.concat(".d.ts"))
case _ => false
}

ModuleNameParser(
source.libName.`__value` +: file.path.relativeTo(source.folder.path).segments.to[List],
keepIndexPath,
)
}

val ret = IArray.fromOptions(shortened, Some(longName))

/* some libraries contain multiple directory trees with type definitions, and refer to just one of them through
* `typings` in package.json for instance.
*
* Remarkably it can reach into one of the other trees even if the current tree has everything needed.
* This resolves the most common case, and fixes antd 4 in particular */
val inParallelDirectory = ret.collect {
case TsIdentModule(scopeOpt, fragments) if fragments.contains("lib") =>
TsIdentModule(scopeOpt, fragments.map { case "lib" => "es"; case other => other })
case TsIdentModule(scopeOpt, fragments) if fragments.contains("es") =>
TsIdentModule(scopeOpt, fragments.map { case "es" => "lib"; case other => other })
def moduleNameFor(source: LibTsSource, file: InFile): TsIdentModule = {
val keepIndexPath = file.path match {
case base / segment / "index.d.ts" => files.exists(base / segment.concat(".d.ts"))
case _ => false
}

ret ++ inParallelDirectory
ModuleNameParser(
source.libName.`__value` +: file.path.relativeTo(source.folder.path).segments.to[List],
keepIndexPath,
)
}

def file(within: InFolder, fragment: String): Option[InFile] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ object PathsFromTsLibSource {
*/
case "amd" => true
case "umd" => true
case "es" => true
case "es6" => true
/* DefinitelyTyped uses this pattern for newer versions of typescript. We just use the default */
case TS() => true
/* DefinitelyTyped uses this pattern for old versions of the library */
Expand Down
Loading