Skip to content

Commit

Permalink
Merge pull request #165 from tylerferrara/imports-fix
Browse files Browse the repository at this point in the history
Fix class imports from node modules
  • Loading branch information
rhys-vdw authored Jan 7, 2022
2 parents e4c1b27 + b275552 commit 0fad41e
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 1 deletion.
25 changes: 24 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1087,15 +1087,38 @@ export function processProject(

outFile.addStatements(functions.join('\n'))

// Memoize imports within local source file
const importsMap = new Map<string, string>()
for (const impDeclaration of sourceFile.getImportDeclarations()) {
impDeclaration.getNamedImports().forEach(impSpecifier => {
importsMap.set(
impSpecifier.getText(),
impDeclaration.getModuleSpecifierValue()
)
})
}

outFile.addImportDeclarations(
Array.from(dependencies.entries()).reduce(
(structures, [importFile, imports]) => {
if (outFile === importFile) {
return structures
}
const moduleSpecifier = outFile.getRelativePathAsModuleSpecifierTo(

let moduleSpecifier = outFile.getRelativePathAsModuleSpecifierTo(
importFile
)

if (importFile.isInNodeModules()) {
// Packages within node_modules should not be referenced via relative path
for (const im in imports) {
const importDeclaration = importsMap.get(im)
if (importDeclaration) {
moduleSpecifier = importDeclaration
}
}
}

const defaultImport = imports.default
delete imports.default
const namedImports = Object.entries(imports).map(([alias, name]) =>
Expand Down
172 changes: 172 additions & 0 deletions tests/import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import test from 'tape'
import path from 'path'
import fs from 'fs'
import { Project } from 'ts-morph'
import { processProject } from '../src'
const WorkingDir = path.dirname(__filename)
const TestFile = 'ImportTest.ts'
const TestFilePath = path.join(WorkingDir, TestFile)

interface TestDefinition {
message: string
inputFile: string
guardFile: string
}

// Test blueprint for running different test definitions
class Blueprint {
inputContents: string
expectedContents: string
message: string
constructor(message: string, inputFile: string, guardFile: string) {
this.inputContents = inputFile
this.expectedContents = guardFile
this.message = message
}
createTestFile() {
fs.writeFileSync(TestFilePath, this.inputContents)
}
deleteTestFile() {
fs.unlinkSync(TestFilePath)
}
buildProject() {
const project = new Project({
skipAddingFilesFromTsConfig: true,
compilerOptions: { strict: true },
useInMemoryFileSystem: false,
})
project.addSourceFileAtPath(TestFilePath)
project.saveSync()
return project
}
run() {
test(this.message, t => {
this.createTestFile()
const project = this.buildProject()
t.doesNotThrow(() => {
processProject(project, { exportAll: true })
})
const guardFile = project.getSourceFiles()[0]
guardFile.formatText()
t.equal(guardFile.getText(), this.expectedContents)
t.end()
this.deleteTestFile()
})
}
}

function genBlueprint(def: TestDefinition) {
return new Blueprint(def.message, def.inputFile, def.guardFile)
}

// Define grouping of tests
const blueprints = [
genBlueprint({
message:
'interfaces from scoped package in node modules requires no import',
inputFile: `import { InMemoryFileSystemHostOptions } from "@ts-morph/common";
export interface Foo {
target: InMemoryFileSystemHostOptions
}`,
guardFile: `import { Foo } from "./ImportTest";
export function isFoo(obj: any, _argumentName?: string): obj is Foo {
return (
(obj !== null &&
typeof obj === "object" ||
typeof obj === "function") &&
(obj.target !== null &&
typeof obj.target === "object" ||
typeof obj.target === "function") &&
(typeof obj.target.skipLoadingLibFiles === "undefined" ||
obj.target.skipLoadingLibFiles === false ||
obj.target.skipLoadingLibFiles === true)
)
}
`,
}),
genBlueprint({
message: 'type from scoped package in node modules requires no import',
inputFile: `import { ResolutionHostFactory } from "@ts-morph/common";
export interface Foo {
target: ResolutionHostFactory
}`,
guardFile: `import { Foo } from "./ImportTest";
export function isFoo(obj: any, _argumentName?: string): obj is Foo {
return (
(obj !== null &&
typeof obj === "object" ||
typeof obj === "function") &&
typeof obj.target === "function"
)
}
`,
}),
genBlueprint({
message: 'using class from scoped package in node modules',
inputFile: `import { CompilerOptionsContainer } from "@ts-morph/common";
export interface Foo {
target: CompilerOptionsContainer
}`,
guardFile: `import { CompilerOptionsContainer } from "@ts-morph/common";
import { Foo } from "./ImportTest";
export function isFoo(obj: any, _argumentName?: string): obj is Foo {
return (
(obj !== null &&
typeof obj === "object" ||
typeof obj === "function") &&
obj.target instanceof CompilerOptionsContainer
)
}
`,
}),
genBlueprint({
message: 'using multiple classes from scoped package in node modules',
inputFile: `import { CompilerOptionsContainer, TsConfigResolver, InMemoryFileSystemHost } from "@ts-morph/common";
export interface Foo {
target: CompilerOptionsContainer,
res: TsConfigResolver,
fs: InMemoryFileSystemHost
}`,
guardFile: `import { CompilerOptionsContainer, TsConfigResolver, InMemoryFileSystemHost } from "@ts-morph/common";
import { Foo } from "./ImportTest";
export function isFoo(obj: any, _argumentName?: string): obj is Foo {
return (
(obj !== null &&
typeof obj === "object" ||
typeof obj === "function") &&
obj.target instanceof CompilerOptionsContainer &&
obj.res instanceof TsConfigResolver &&
obj.fs instanceof InMemoryFileSystemHost
)
}
`,
}),
genBlueprint({
message: 'using class from unscoped package in node modules',
inputFile: `import { Directory } from "ts-morph";
export interface Foo {
dir: Directory
}`,
guardFile: `import { Directory } from "ts-morph";
import { Foo } from "./ImportTest";
export function isFoo(obj: any, _argumentName?: string): obj is Foo {
return (
(obj !== null &&
typeof obj === "object" ||
typeof obj === "function") &&
obj.dir instanceof Directory
)
}
`,
}),
]

// Run all tests
blueprints.forEach(bp => {
bp.run()
})

0 comments on commit 0fad41e

Please sign in to comment.