Skip to content

Commit

Permalink
Improve support for Typescript declare structures
Browse files Browse the repository at this point in the history
  • Loading branch information
christophercurrie committed May 11, 2019
1 parent 1edbbd0 commit aa290bb
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 94 deletions.
15 changes: 15 additions & 0 deletions src/ExportMap.js
Expand Up @@ -464,6 +464,7 @@ ExportMap.parse = function (path, content, context) {
case 'ClassDeclaration':
case 'TypeAlias': // flowtype with babel-eslint parser
case 'InterfaceDeclaration':
case 'TSDeclareFunction':
case 'TSEnumDeclaration':
case 'TSTypeAliasDeclaration':
case 'TSInterfaceDeclaration':
Expand Down Expand Up @@ -509,6 +510,20 @@ ExportMap.parse = function (path, content, context) {
m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) })
})
}

// This doesn't declare anything, but changes what's being exported.
if (n.type === 'TSExportAssignment') {
const d = ast.body.find(
b => b.type === 'TSModuleDeclaration' && b.id.name === n.expression.name
)
if (d && d.body && d.body.body) {
d.body.body.forEach(b => {
// Export-assignment exports all members in the namespace, explicitly exported or not.
const s = b.type === 'ExportNamedDeclaration' ? b.declaration : b
m.namespace.set(s.id.name, captureDoc(source, docStyleParsers, b))
})
}
}
})

return m
Expand Down
33 changes: 33 additions & 0 deletions tests/files/typescript-declare.d.ts
@@ -0,0 +1,33 @@
export declare type MyType = string
export declare enum MyEnum {
Foo,
Bar,
Baz
}
export declare interface Foo {
native: string | number
typedef: MyType
enum: MyEnum
}

export declare abstract class Bar {
abstract foo(): Foo

method();
}

export declare function getFoo() : MyType;

export declare module MyModule {
export function ModuleFunction();
}

export declare namespace MyNamespace {
export function NamespaceFunction();

export module NSModule {
export function NSModuleFunction();
}
}

interface NotExported {}
39 changes: 39 additions & 0 deletions tests/files/typescript-export-assign.d.ts
@@ -0,0 +1,39 @@
export = AssignedNamespace;

declare namespace AssignedNamespace {
type MyType = string
enum MyEnum {
Foo,
Bar,
Baz
}

interface Foo {
native: string | number
typedef: MyType
enum: MyEnum
}

abstract class Bar {
abstract foo(): Foo

method();
}

export function getFoo() : MyType;

export module MyModule {
export function ModuleFunction();
}

export namespace MyNamespace {
export function NamespaceFunction();

export module NSModule {
export function NSModuleFunction();
}
}

// Export-assignment exports all members in the namespace, explicitly exported or not.
// interface NotExported {}
}
186 changes: 94 additions & 92 deletions tests/src/rules/named.js
Expand Up @@ -292,98 +292,100 @@ context('Typescript', function () {
}

parsers.forEach((parser) => {
ruleTester.run('named', rule, {
valid: [
test({
code: 'import { MyType } from "./typescript"',
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: 'import { Foo } from "./typescript"',
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: 'import { Bar } from "./typescript"',
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: 'import { getFoo } from "./typescript"',
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: 'import { MyEnum } from "./typescript"',
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `
import { MyModule } from "./typescript"
MyModule.ModuleFunction()
`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `
import { MyNamespace } from "./typescript"
MyNamespace.NSModule.NSModuleFunction()
`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
],

invalid: [
test({
code: 'import { MissingType } from "./typescript"',
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
errors: [{
message: "MissingType not found in './typescript'",
type: 'Identifier',
}],
}),
test({
code: 'import { NotExported } from "./typescript"',
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
errors: [{
message: "NotExported not found in './typescript'",
type: 'Identifier',
}],
}),
],
['typescript', 'typescript-declare', 'typescript-export-assign'].forEach((source) => {
ruleTester.run(`named`, rule, {
valid: [
test({
code: `import { MyType } from "./${source}"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `import { Foo } from "./${source}"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `import { Bar } from "./${source}"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `import { getFoo } from "./${source}"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `import { MyEnum } from "./${source}"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `
import { MyModule } from "./${source}"
MyModule.ModuleFunction()
`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
test({
code: `
import { MyNamespace } from "./${source}"
MyNamespace.NSModule.NSModuleFunction()
`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
}),
],

invalid: [
test({
code: `import { MissingType } from "./${source}"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
errors: [{
message: `MissingType not found in './${source}'`,
type: 'Identifier',
}],
}),
test({
code: `import { NotExported } from "./${source}"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
errors: [{
message: `NotExported not found in './${source}'`,
type: 'Identifier',
}],
}),
],
})
})
})
})
4 changes: 2 additions & 2 deletions utils/unambiguous.js
Expand Up @@ -2,7 +2,7 @@
exports.__esModule = true


const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*]))/m
const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m
/**
* detect possible imports/exports without a full parse.
*
Expand All @@ -18,7 +18,7 @@ exports.test = function isMaybeUnambiguousModule(content) {
}

// future-/Babel-proof at the expense of being a little loose
const unambiguousNodeType = /^(Exp|Imp)ort.*Declaration$/
const unambiguousNodeType = /^(((Exp|Imp)ort.*Declaration)|TSExportAssignment)$/

/**
* Given an AST, return true if the AST unambiguously represents a module.
Expand Down

0 comments on commit aa290bb

Please sign in to comment.