Skip to content

Commit

Permalink
Merge pull request #1356 from christophercurrie/typescript-declare
Browse files Browse the repository at this point in the history
Improve support for Typescript declare structures
  • Loading branch information
ljharb committed May 23, 2019
2 parents c8ac7ff + b52bf3e commit 17beb33
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 94 deletions.
30 changes: 30 additions & 0 deletions src/ExportMap.js
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,34 @@ 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))
)
)
} else {
m.namespace.set(
exportedDecl.id.name,
captureDoc(source, docStyleParsers, moduleBlockNode))
}
})
}
}
})

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 17beb33

Please sign in to comment.