stencil-eslint-core
Version:
ESLint utils to help create custom rules for Stencil JS projects.
177 lines (172 loc) • 6.06 kB
JavaScript
'use strict';
var ts = require('typescript');
var eslintUtils = require('eslint-utils');
var eslint = require('eslint');
const SyntaxKind = ts.SyntaxKind;
function isPrivate(originalNode) {
if (originalNode.modifiers) {
return originalNode.modifiers.some((m) => (m.kind === ts.SyntaxKind.PrivateKeyword ||
m.kind === ts.SyntaxKind.ProtectedKeyword));
}
// detect private identifier (#)
const firstChildNode = originalNode.getChildAt(0);
return firstChildNode ? firstChildNode.kind === SyntaxKind.PrivateIdentifier : false;
}
function getDecorator(node, decoratorName) {
if (decoratorName) {
return node.decorators && node.decorators.find(isDecoratorNamed(decoratorName));
}
return node.decorators ? node.decorators.filter((dec) => dec.expression) : [];
}
function parseDecorator(decorator) {
if (decorator && decorator.expression && decorator.expression.type === 'CallExpression') {
return decorator.expression.arguments.map((a) => {
const parsed = eslintUtils.getStaticValue(a);
return parsed ? parsed.value : undefined;
});
}
return [];
}
function decoratorName(dec) {
return dec.expression && dec.expression.callee.name;
}
function isDecoratorNamed(propName) {
return (dec) => {
return decoratorName(dec) === propName;
};
}
function stencilComponentContext() {
let componentNode;
return {
rules: {
'ClassDeclaration': (node) => {
const component = getDecorator(node, 'Component');
if (component) {
componentNode = component;
}
},
'ClassDeclaration:exit': (node) => {
if (componentNode === node) {
componentNode = undefined;
}
}
},
isComponent() {
return !!componentNode;
}
};
}
function getType(node) {
return node.typeAnnotation.typeAnnotation.typeName.name;
}
const stencilDecorators = ['Component', 'Prop', 'State', 'Watch', 'Element', 'Method', 'Event', 'Listen'];
const stencilLifecycle = [
'connectedCallback',
'disconnectedCallback',
'componentWillLoad',
'componentDidLoad',
'componentWillRender',
'componentDidRender',
'componentShouldUpdate',
'componentWillUpdate',
'componentDidUpdate',
'render'
];
const TOKEN_TO_TEXT = {
[]: '{',
[]: '}',
[]: '(',
[]: ')',
[]: '[',
[]: ']',
[]: '.',
[]: '...',
[]: ',',
[]: ',',
[]: '<',
[]: '>',
[]: '<=',
[]: '>=',
[]: '==',
[]: '!=',
[]: '===',
[]: 'instanceof',
[]: '!==',
[]: '=>',
[]: '+',
[]: '-',
[]: '*',
[]: '**',
[]: '/',
[]: '%',
[]: '++',
[]: '--',
[]: '<<',
[]: '</',
[]: '>>',
[]: '>>>',
[]: '&',
[]: '|',
[]: '^',
[]: '!',
[]: '~',
[]: '&&',
[]: '||',
[]: '?',
[]: ':',
[]: '=',
[]: '+=',
[]: '-=',
[]: '*=',
[]: '**=',
[]: '/=',
[]: '%=',
[]: '<<=',
[]: '>>=',
[]: '>>>=',
[]: '&=',
[]: '|=',
[]: '^=',
[]: '@',
[]: 'in',
[]: 'unique',
[]: 'keyof',
[]: 'new',
[]: 'import',
[]: 'readonly'
};
/**
* Returns the string form of the given TSToken SyntaxKind
* @param kind the token's SyntaxKind
* @returns the token applicable token as a string
*/
function getTextForTokenKind(kind) {
return TOKEN_TO_TEXT[kind];
}
const ruleTester = (projectPath) => new eslint.RuleTester({
parser: require.resolve("@typescript-eslint/parser"),
parserOptions: {
ecmaVersion: 2017,
sourceType: 'module',
ecmaFeatures: {
jsx: true
},
project: projectPath,
env: {
browser: true,
node: true,
es6: true
}
}
});
exports.decoratorName = decoratorName;
exports.getDecorator = getDecorator;
exports.getTextForTokenKind = getTextForTokenKind;
exports.getType = getType;
exports.isDecoratorNamed = isDecoratorNamed;
exports.isPrivate = isPrivate;
exports.parseDecorator = parseDecorator;
exports.ruleTester = ruleTester;
exports.stencilComponentContext = stencilComponentContext;
exports.stencilDecorators = stencilDecorators;
exports.stencilLifecycle = stencilLifecycle;