Skip to content

Commit

Permalink
add support for @customElement decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
43081j committed May 13, 2019
1 parent 0da36a2 commit d00c889
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/rules/no-constructor-attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const rule: Rule.RuleModule = {

return {
'ClassDeclaration,ClassExpression': (node: ESTree.Node): void => {
debugger;
if (
(node.type === 'ClassExpression' ||
node.type === 'ClassDeclaration') &&
Expand Down
16 changes: 16 additions & 0 deletions src/test/rules/attach-shadow-constructor_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ ruleTester.run('attach-shadow-constructor', rule, {
column: 11
}
]
},
{
code: `@customElement('x-foo')
class A extends Element {
connectedCallback() {
this.attachShadow();
}
}`,
parser: '@typescript-eslint/parser',
errors: [
{
messageId: 'attachShadowConstructor',
line: 4,
column: 11
}
]
}
]
});
16 changes: 16 additions & 0 deletions src/test/rules/guard-super-call_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ ruleTester.run('guard-super-call', rule, {
column: 11
}
]
},
{
code: `@customElement('x-foo')
class A extends Element {
connectedCallback() {
super.connectedCallback();
}
}`,
parser: '@typescript-eslint/parser',
errors: [
{
messageId: 'guardSuperCall',
line: 4,
column: 11
}
]
}
]
});
19 changes: 19 additions & 0 deletions src/test/rules/no-constructor-attributes_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,25 @@ ruleTester.run('no-constructor-attributes', rule, {
column: 11
}
]
},
{
code: `@customElement('x-foo')
class Foo extends Bar {
constructor() {
super();
this.setAttribute('x', 'y');
}
}`,
parser: '@typescript-eslint/parser',
errors: [
{
message:
'Attributes must not be interacted with in the ' +
'constructor as the element may not be ready yet.',
line: 5,
column: 11
}
]
}
]
});
16 changes: 16 additions & 0 deletions src/test/rules/no-self-class_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,22 @@ ruleTester.run('no-self-class', rule, {
column: 11
}
]
},
{
code: `@customElement('x-foo')
class Foo extends Bar {
method() {
this.className += 'test';
}
}`,
parser: '@typescript-eslint/parser',
errors: [
{
messageId: 'selfClass',
line: 4,
column: 11
}
]
}
]
});
19 changes: 18 additions & 1 deletion src/test/rules/no-typos_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,26 @@ ruleTester.run('no-typos', rule, {
},
{
code: `/** @customElement **/
class Foo extends HTMLElement {
class Foo extends Bar {
static get observedAtributes() {}
}`,
errors: [
{
messageId: 'member',
data: {
replacement: 'observedAttributes'
},
line: 3,
column: 22
}
]
},
{
code: `@customElement('x-foo')
class Foo extends Bar {
static get observedAtributes() {}
}`,
parser: '@typescript-eslint/parser',
errors: [
{
messageId: 'member',
Expand Down
60 changes: 52 additions & 8 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
import * as ESTree from 'estree';
import {AST} from 'eslint';

export interface DecoratorNode extends ESTree.BaseNode {
type: 'Decorator';
expression: ESTree.Expression;
}

export type WithDecorators<T extends ESTree.Node> = T & {
decorators?: DecoratorNode[];
};

/**
* Determines if a given decorator is the `@customElement` decorator
*
* @param {DecoratorNode} node Decorator to test
* @return {boolean}
*/
export function isCustomElementDecorator(node: DecoratorNode): boolean {
return (
(node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'Identifier' &&
node.expression.callee.name === 'customElement') ||
(node.expression.type === 'Identifier' &&
node.expression.name === 'customElement')
);
}

/**
* Determines if a node is an element class or not.
*
Expand All @@ -12,15 +37,34 @@ export function isCustomElement(
node: ESTree.Node,
jsdoc?: AST.Token | null
): node is ESTree.Class {
return (
(node.type === 'ClassExpression' || node.type === 'ClassDeclaration') &&
((node.superClass &&
const asDecorated = node as WithDecorators<ESTree.Node>;

if (node.type === 'ClassExpression' || node.type === 'ClassDeclaration') {
if (
node.superClass &&
node.superClass.type === 'Identifier' &&
node.superClass.name === 'HTMLElement') ||
(jsdoc !== undefined &&
jsdoc !== null &&
jsdoc.value.includes('@customElement')))
);
node.superClass.name === 'HTMLElement'
) {
return true;
}

if (
jsdoc !== undefined &&
jsdoc !== null &&
jsdoc.value.includes('@customElement')
) {
return true;
}

if (
asDecorated.decorators !== undefined &&
asDecorated.decorators.some(isCustomElementDecorator)
) {
return true;
}
}

return false;
}

/**
Expand Down

0 comments on commit d00c889

Please sign in to comment.