diff --git a/docs/rules/no-exports-with-element.md b/docs/rules/no-exports-with-element.md index 2237ca8..280e632 100644 --- a/docs/rules/no-exports-with-element.md +++ b/docs/rules/no-exports-with-element.md @@ -4,7 +4,7 @@ It's possible to export multiple functions and classes in a JavaScript file. In ## Rule Details -This rule disallows exports (other than the element class) in a file with a Custom Element. +This rule disallows exports (other than the element class and event subclasses) in a file with a Custom Element. 👎 Examples of **incorrect** code for this rule: @@ -29,6 +29,18 @@ export class FooBarElement extends HTMLElement { } ``` +```js +// foo-bar-element.js +import {myHelper} from './helpers.js' +export class FooReadyEvent extends Event { + // ... +} + +export class FooBarElement extends HTMLElement { + // ... +} +``` + ```js // helpers.js export function myHelper() { diff --git a/lib/class-ref-tracker.js b/lib/class-ref-tracker.js index 849256f..a28e116 100644 --- a/lib/class-ref-tracker.js +++ b/lib/class-ref-tracker.js @@ -48,6 +48,10 @@ class ClassRefTracker { static customElements(context) { return new ClassRefTracker(context, superClassRef => superClassRef && /^HTML.*Element$/.test(superClassRef.name)) } + + static customEvents(context) { + return new ClassRefTracker(context, superClassRef => superClassRef && superClassRef.name === 'Event') + } } module.exports = ClassRefTracker diff --git a/lib/custom-selectors.js b/lib/custom-selectors.js index ad9609c..d2e82d8 100644 --- a/lib/custom-selectors.js +++ b/lib/custom-selectors.js @@ -1,4 +1,5 @@ const HTMLElementClass = ':matches(ClassDeclaration, ClassExpression)[superClass.name=/HTML.*Element/]' +const EventSubClass = ':matches(ClassDeclaration, ClassExpression)[superClass.name=/^Event$/]' const customElements = { _call: '[callee.object.type=Identifier][callee.object.name=customElements],' + @@ -10,4 +11,5 @@ customElements.define = `CallExpression[callee.property.name=define]:matches(${c module.exports = { HTMLElementClass, customElements, + EventSubClass, } diff --git a/lib/rules/no-exports-with-element.js b/lib/rules/no-exports-with-element.js index ba4ae02..e1549b0 100644 --- a/lib/rules/no-exports-with-element.js +++ b/lib/rules/no-exports-with-element.js @@ -9,6 +9,7 @@ module.exports = { schema: [], create(context) { const classes = ClassRefTracker.customElements(context) + const eventClasses = ClassRefTracker.customEvents(context) const exports = new Set() let hasElement = false @@ -17,23 +18,26 @@ module.exports = { hasElement = true classes.add(node) }, + [s.EventSubClass](node) { + eventClasses.add(node) + }, ['ExportNamedDeclaration > VariableDeclaration > VariableDeclarator']: function (node) { - if (!classes.get(node.init)) { + if (!classes.get(node.init) && !eventClasses.get(node.init)) { exports.add(node.init) } }, ['ExportNamedDeclaration :matches(ClassDeclaration, ClassExpression, FunctionDeclaration)']: function (node) { - if (!classes.get(node)) { + if (!classes.get(node) && !eventClasses.get(node)) { exports.add(node) } }, ['ExportNamedDeclaration > AssignmentExpression']: function (node) { - if (!classes.get(node.right)) { + if (!classes.get(node.right) && !eventClasses.get(node.local)) { exports.add(node.right) } }, ['ExportNamedDeclaration ExportSpecifier']: function (node) { - if (!classes.get(node.local)) { + if (!classes.get(node.local) && !eventClasses.get(node.local)) { exports.add(node.local) } }, @@ -42,7 +46,7 @@ module.exports = { if (declaration.type === 'AssignmentExpression') { declaration = declaration.right } - if (!classes.get(declaration)) { + if (!classes.get(declaration) && !eventClasses.get(declaration)) { exports.add(declaration) } }, diff --git a/test/no-exports-with-element.js b/test/no-exports-with-element.js index 12aa17f..a8d4d6c 100644 --- a/test/no-exports-with-element.js +++ b/test/no-exports-with-element.js @@ -10,6 +10,10 @@ ruleTester.run('no-exports-with-element', rule, { {code: 'class FooBarElement extends HTMLElement { }\nexport {FooBarElement}'}, {code: 'class FooBarElement extends HTMLElement { }\nexport default FooBarElement'}, {code: 'export class a extends HTMLElement { }\nexport class b extends HTMLElement { }'}, + {code: 'export class a extends HTMLElement { }\nexport class b extends Event { }'}, + {code: 'export class a extends HTMLElement { }\nexport default class b extends Event { }'}, + {code: 'class A extends HTMLElement { }\nclass B extends Event { }\nexport {A, B}'}, + {code: 'class A extends HTMLElement { }\nclass B extends Event { }\nexport default A\nexport {B}'}, {code: 'export function baz() { const foo = "bar" }'}, { code: 'export class a extends Map { }\nexport default class extends Map { }\nexport const b = class extends Map {}',