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
@@ -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':
@@ -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))
This conversation was marked as resolved by christophercurrie

This comment has been minimized.

Copy link
@ljharb

ljharb May 13, 2019

Collaborator

if each item can’t fit on one line, please put each on its own line:

Suggested change
id.name, captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode))
id.name,
captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode))
)
)
} else {
m.namespace.set(exportedDecl.id.name,
This conversation was marked as resolved by christophercurrie

This comment has been minimized.

Copy link
@ljharb

ljharb May 13, 2019

Collaborator
Suggested change
m.namespace.set(exportedDecl.id.name,
m.namespace.set(
exportedDecl.id.name,
captureDoc(source, docStyleParsers, moduleBlockNode))
}
})
}
}
})

return m
@@ -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 {}
@@ -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 {}
}
@@ -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',
}],
}),
],
})
})
})
})
@@ -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.
*
@@ -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.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.