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

Improve support for Typescript declare structures #1356

Merged
merged 7 commits into from
May 23, 2019
Merged
Show file tree
Hide file tree
Changes from 5 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
28 changes: 28 additions & 0 deletions src/ExportMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ ExportMap.parse = function (path, content, context) {
case 'ClassDeclaration':
case 'TypeAlias': // flowtype with babel-eslint parser
case 'InterfaceDeclaration':
case 'DeclareFunction':
case 'TSDeclareFunction':
case 'TSEnumDeclaration':
case 'TSTypeAliasDeclaration':
case 'TSInterfaceDeclaration':
Expand Down Expand Up @@ -509,6 +511,32 @@ 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 moduleDecl = ast.body.find((bodyNode) =>
bodyNode.type === 'TSModuleDeclaration' && bodyNode.id.name === n.expression.name
)
if (moduleDecl && moduleDecl.body && moduleDecl.body.body) {
moduleDecl.body.body.forEach((moduleBlockNode) => {
// Export-assignment exports all members in the namespace, explicitly exported or not.
const exportedDecl = moduleBlockNode.type === 'ExportNamedDeclaration' ?
moduleBlockNode.declaration :
moduleBlockNode

if (exportedDecl.type === 'VariableDeclaration') {
exportedDecl.declarations.forEach((decl) =>
recursivePatternCapture(decl.id,(id) => m.namespace.set(
id.name, captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode))
christophercurrie marked this conversation as resolved.
Show resolved Hide resolved
)
)
} else {
m.namespace.set(exportedDecl.id.name,
christophercurrie marked this conversation as resolved.
Show resolved Hide resolved
captureDoc(source, docStyleParsers, moduleBlockNode))
}
})
}
}
})

return m
Expand Down
33 changes: 33 additions & 0 deletions tests/files/typescript-declare.d.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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