@agilebot/eslint-plugin 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,87 +0,0 @@
1
- module.exports = {
2
- meta: {
3
- docs: {
4
- description: 'Disallow style props on components and DOM Nodes',
5
- category: 'Best Practices',
6
- recommended: false
7
- },
8
- messages: {
9
- disallowInlineStyles:
10
- 'Avoid using inline styles, use sx prop or tss-react or styled-component instead'
11
- },
12
- schema: [
13
- {
14
- type: 'object',
15
- properties: {
16
- allowedFor: {
17
- type: 'array',
18
- uniqueItems: true,
19
- items: { type: 'string' }
20
- }
21
- }
22
- }
23
- ]
24
- },
25
-
26
- create(context) {
27
- const configuration = context.options[0] || {};
28
- const allowedFor = configuration.allowedFor || [];
29
-
30
- function checkComponent(node) {
31
- const parentName = node.parent.name;
32
- // Extract a component name when using a "namespace", e.g. `<AntdLayout.Content />`.
33
- const tag =
34
- parentName.name ||
35
- `${parentName.object.name}.${parentName.property.name}`;
36
- const componentName = parentName.name || parentName.property.name;
37
- if (
38
- componentName &&
39
- typeof componentName[0] === 'string' &&
40
- componentName[0] !== componentName[0].toUpperCase()
41
- ) {
42
- // This is a DOM node, not a Component, so exit.
43
- return;
44
- }
45
-
46
- if (allowedFor.includes(tag)) {
47
- return;
48
- }
49
- const prop = node.name.name;
50
-
51
- if (prop === 'style') {
52
- context.report({
53
- node,
54
- messageId: 'disallowInlineStyles'
55
- });
56
- }
57
- }
58
-
59
- function checkDOMNodes(node) {
60
- const tag = node.parent.name.name;
61
- if (
62
- !(tag && typeof tag === 'string' && tag[0] !== tag[0].toUpperCase())
63
- ) {
64
- // This is a Component, not a DOM node, so exit.
65
- return;
66
- }
67
- if (allowedFor.includes(tag)) {
68
- return;
69
- }
70
- const prop = node.name.name;
71
-
72
- if (prop === 'style') {
73
- context.report({
74
- node,
75
- messageId: 'disallowInlineStyles'
76
- });
77
- }
78
- }
79
-
80
- return {
81
- JSXAttribute(node) {
82
- checkComponent(node);
83
- checkDOMNodes(node);
84
- }
85
- };
86
- }
87
- };
@@ -1,105 +0,0 @@
1
- const { ESLintUtils } = require('@typescript-eslint/utils');
2
-
3
- /**
4
- * Auto-fix util.
5
- * Ensures that passed key is imported from 'react' package.
6
- */
7
- function* updateImportStatement(context, fixer, key) {
8
- const sourceCode = context.getSourceCode();
9
- const importNode = sourceCode.ast.body.find(
10
- node => node.type === 'ImportDeclaration' && node.source.value === 'react'
11
- );
12
-
13
- // No import from 'react' - create import statement
14
- if (!importNode) {
15
- yield fixer.insertTextBefore(
16
- sourceCode.ast.body[0],
17
- `import { ${key} } from 'react';\n`
18
- );
19
-
20
- return;
21
- }
22
-
23
- // Only default import from 'react' - add named imports section
24
- if (
25
- importNode.specifiers.length === 1 &&
26
- importNode.specifiers[0].type === 'ImportDefaultSpecifier'
27
- ) {
28
- yield fixer.insertTextAfter(importNode.specifiers[0], `, { ${key} }`);
29
-
30
- return;
31
- }
32
-
33
- const alreadyImportedKeys = importNode.specifiers
34
- .filter(specifier => specifier.type === 'ImportSpecifier')
35
- .map(specifier => specifier.imported.name);
36
-
37
- // Named imports section is present and current key is already imported - do nothing
38
- if (alreadyImportedKeys.includes(key)) {
39
- return;
40
- }
41
-
42
- // Named imports section is present and current key is not imported yet - add it to named imports section
43
- yield fixer.insertTextAfter([...importNode.specifiers].pop(), `, ${key}`);
44
- }
45
-
46
- /** @type {import('eslint').Rule.RuleModule} */
47
- module.exports = ESLintUtils.RuleCreator.withoutDocs({
48
- defaultOptions: [],
49
- meta: {
50
- type: 'layout',
51
- fixable: 'code',
52
- docs: {
53
- description:
54
- 'Enforce importing each member of React namespace separately instead of accessing them through React namespace',
55
- category: 'Layout & Formatting'
56
- },
57
- messages: {
58
- illegalReactPropertyAccess:
59
- 'Illegal React property access: {{name}}. Use named import instead.'
60
- }
61
- },
62
-
63
- create(context) {
64
- return {
65
- // Analyze TS types declarations
66
- TSQualifiedName(node) {
67
- // Do nothing to types that are ending with 'Event' as they will overlap with global event types otherwise
68
- if (node.left.name !== 'React' || node.right.name.endsWith('Event')) {
69
- return;
70
- }
71
-
72
- context.report({
73
- node,
74
- messageId: 'illegalReactPropertyAccess',
75
- data: {
76
- name: node.right.name
77
- },
78
- *fix(fixer) {
79
- yield fixer.replaceText(node, node.right.name);
80
- yield* updateImportStatement(context, fixer, node.right.name);
81
- }
82
- });
83
- },
84
-
85
- // Analyze expressions for React.* access
86
- MemberExpression(node) {
87
- if (node.object.name !== 'React') {
88
- return;
89
- }
90
-
91
- context.report({
92
- node,
93
- messageId: 'illegalReactPropertyAccess',
94
- data: {
95
- name: node.property.name
96
- },
97
- *fix(fixer) {
98
- yield fixer.replaceText(node, node.property.name);
99
- yield* updateImportStatement(context, fixer, node.property.name);
100
- }
101
- });
102
- }
103
- };
104
- }
105
- });
@@ -1,43 +0,0 @@
1
- const { getStyesObj, isCamelCase } = require('../../util/tss');
2
-
3
- module.exports = {
4
- meta: {
5
- type: 'problem'
6
- },
7
- create: function rule(context) {
8
- return {
9
- CallExpression(node) {
10
- const stylesObj = getStyesObj(node);
11
-
12
- if (stylesObj === undefined) {
13
- return;
14
- }
15
-
16
- stylesObj.properties.forEach(property => {
17
- if (property.computed) {
18
- // Skip over computed properties for now.
19
- // e.g. `{ [foo]: { ... } }`
20
- return;
21
- }
22
-
23
- if (
24
- property.type === 'ExperimentalSpreadProperty' ||
25
- property.type === 'SpreadElement'
26
- ) {
27
- // Skip over object spread for now.
28
- // e.g. `{ ...foo }`
29
- return;
30
- }
31
-
32
- const className = property.key.value || property.key.name;
33
- if (!isCamelCase(className)) {
34
- context.report(
35
- property,
36
- `Class \`${className}\` must be camelCase in makeStyles.`
37
- );
38
- }
39
- });
40
- }
41
- };
42
- }
43
- };
@@ -1,58 +0,0 @@
1
- const { getStyesObj } = require('../../util/tss');
2
-
3
- module.exports = {
4
- meta: {
5
- type: 'problem',
6
- docs: {
7
- description:
8
- 'Enforce the use of color variables instead of color codes within makeStyles'
9
- }
10
- },
11
- create: function (context) {
12
- const parserOptions = context.parserOptions;
13
- if (!parserOptions || !parserOptions.project) {
14
- return {};
15
- }
16
-
17
- return {
18
- CallExpression(node) {
19
- const stylesObj = getStyesObj(node);
20
- if (!stylesObj) {
21
- return;
22
- }
23
-
24
- // Check for color codes inside the stylesObj
25
- function checkColorLiteral(value) {
26
- if (value.type === 'Literal' && typeof value.value === 'string') {
27
- const colorCodePattern =
28
- /#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgb\?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*\d*(?:\.\d+)?)?\s*\)/g;
29
- const isColorCode = colorCodePattern.test(value.value);
30
- if (isColorCode) {
31
- context.report({
32
- node: value,
33
- message:
34
- 'Use color variables instead of color codes in makeStyles.'
35
- });
36
- }
37
- }
38
- }
39
-
40
- function loopStylesObj(obj) {
41
- if (obj && obj.type === 'ObjectExpression') {
42
- obj.properties.forEach(property => {
43
- if (property.type === 'Property' && property.value) {
44
- if (property.value.type === 'ObjectExpression') {
45
- loopStylesObj(property.value);
46
- } else {
47
- checkColorLiteral(property.value);
48
- }
49
- }
50
- });
51
- }
52
- }
53
-
54
- loopStylesObj(stylesObj);
55
- }
56
- };
57
- }
58
- };
@@ -1,108 +0,0 @@
1
- const { getBasicIdentifier, getStyesObj } = require('../../util/tss');
2
-
3
- module.exports = {
4
- meta: {
5
- type: 'problem'
6
- },
7
- create: function rule(context) {
8
- const usedClasses = {};
9
- const definedClasses = {};
10
-
11
- return {
12
- CallExpression(node) {
13
- const stylesObj = getStyesObj(node);
14
-
15
- if (stylesObj === undefined) {
16
- return;
17
- }
18
-
19
- stylesObj.properties.forEach(property => {
20
- if (property.computed) {
21
- // Skip over computed properties for now.
22
- // e.g. `{ [foo]: { ... } }`
23
- return;
24
- }
25
-
26
- if (
27
- property.type === 'ExperimentalSpreadProperty' ||
28
- property.type === 'SpreadElement'
29
- ) {
30
- // Skip over object spread for now.
31
- // e.g. `{ ...foo }`
32
- return;
33
- }
34
- definedClasses[property.key.value || property.key.name] = property;
35
- });
36
- },
37
-
38
- MemberExpression(node) {
39
- if (
40
- node.object.type === 'Identifier' &&
41
- node.object.name === 'classes'
42
- ) {
43
- const whichClass = getBasicIdentifier(node.property);
44
- if (whichClass) {
45
- usedClasses[whichClass] = true;
46
- }
47
- return;
48
- }
49
-
50
- const classIdentifier = getBasicIdentifier(node.property);
51
- if (!classIdentifier) {
52
- // props['foo' + bar].baz
53
- return;
54
- }
55
-
56
- if (classIdentifier !== 'classes') {
57
- // props.foo.bar
58
- return;
59
- }
60
-
61
- const { parent } = node;
62
-
63
- if (parent.type !== 'MemberExpression') {
64
- // foo.styles
65
- return;
66
- }
67
-
68
- if (
69
- node.object.object &&
70
- node.object.object.type !== 'ThisExpression'
71
- ) {
72
- // foo.foo.styles
73
- return;
74
- }
75
-
76
- const propsIdentifier = getBasicIdentifier(parent.object);
77
- if (propsIdentifier && propsIdentifier !== 'props') {
78
- return;
79
- }
80
- if (!propsIdentifier && parent.object.type !== 'MemberExpression') {
81
- return;
82
- }
83
-
84
- if (parent.parent.type === 'MemberExpression') {
85
- // this.props.props.styles
86
- return;
87
- }
88
-
89
- const parentClassIdentifier = getBasicIdentifier(parent.property);
90
- if (parentClassIdentifier) {
91
- usedClasses[parentClassIdentifier] = true;
92
- }
93
- },
94
- 'Program:exit': () => {
95
- // Now we know all of the defined classes and used classes, so we can
96
- // see if there are any defined classes that are not used.
97
- Object.keys(definedClasses).forEach(definedClassKey => {
98
- if (!usedClasses[definedClassKey]) {
99
- context.report(
100
- definedClasses[definedClassKey],
101
- `Class \`${definedClassKey}\` is unused`
102
- );
103
- }
104
- });
105
- }
106
- };
107
- }
108
- };
package/lib/util/intl.js DELETED
@@ -1,127 +0,0 @@
1
- /**
2
- * Finds an attribute in formatMessage using attribute name.
3
- *
4
- * @param {Object} node - parent formatMessage node
5
- * @param {string} attrName - attribute name.
6
- * @returns {Object} node - returns node if it finds the attribute.
7
- */
8
- function findFormatMessageAttrNode(node, attrName) {
9
- // Find formatMessage usages
10
- if (
11
- node.type === 'CallExpression' &&
12
- (node.callee.name === 'formatMessage' || node.callee.name === '$t') &&
13
- node.arguments.length > 0 &&
14
- node.arguments[0].properties
15
- ) {
16
- return node.arguments[0].properties.find(
17
- a => a.key && a.key.name === attrName
18
- );
19
- }
20
-
21
- // Find intl.formatMessage usages
22
- if (
23
- node.type === 'CallExpression' &&
24
- node.callee.type === 'MemberExpression' &&
25
- (node.callee.object.name === 'intl' ||
26
- (node.callee.object.name && node.callee.object.name.endsWith('Intl'))) &&
27
- (node.callee.property.name === 'formatMessage' ||
28
- node.callee.property.name === '$t')
29
- ) {
30
- return node.arguments[0].properties.find(
31
- a => a.key && a.key.name === attrName
32
- );
33
- }
34
- }
35
-
36
- /**
37
- * Finds an attribute in FormattedMessage using attribute name.
38
- *
39
- * @param {Object} node - parent FormattedMessage node
40
- * @param {string} attrName - attribute name.
41
- * @returns {Object} node - returns node if it finds the attribute.
42
- */
43
- function findFormattedMessageAttrNode(node, attrName) {
44
- if (
45
- node.type === 'JSXIdentifier' &&
46
- node.name === 'FormattedMessage' &&
47
- node.parent &&
48
- node.parent.type === 'JSXOpeningElement'
49
- ) {
50
- return node.parent.attributes.find(a => a.name && a.name.name === attrName);
51
- }
52
- }
53
-
54
- /**
55
- * Finds an attribute in defineMessages using attribute name.
56
- *
57
- * @param {Object} node - parent defineMessages node
58
- * @param {string} attrName - attribute name.
59
- * @returns {Object} node - returns node if it finds the attribute.
60
- */
61
- function findAttrNodeInDefineMessages(node, attrName) {
62
- if (
63
- node.type === 'Property' &&
64
- node.key.name === attrName &&
65
- node.parent &&
66
- node.parent.parent &&
67
- node.parent.parent.parent &&
68
- node.parent.parent.parent.parent &&
69
- node.parent.parent.parent.parent.type === 'CallExpression' &&
70
- node.parent.parent.parent.parent.callee.name === 'defineMessages'
71
- ) {
72
- return node;
73
- }
74
- }
75
-
76
- /**
77
- * Finds an attribute in defineMessages using attribute name.
78
- *
79
- * @param {Object} node - parent defineMessages node
80
- * @param {string} attrName - attribute name.
81
- * @returns {Object} node - returns node if it finds the attribute.
82
- */
83
- function findAttrNodeInDefineMessage(node, attrName) {
84
- if (
85
- node.type === 'Property' &&
86
- node.key.name === attrName &&
87
- node.parent &&
88
- node.parent.parent &&
89
- node.parent.parent.type === 'CallExpression' &&
90
- node.parent.parent.callee.name === 'defineMessage'
91
- ) {
92
- return node;
93
- }
94
- }
95
-
96
- /**
97
- * Returns a sorted array of nodes, based on their starting posting in the locale id.
98
- *
99
- * @param {Object} node - parent node containing the locale id.
100
- * @returns {Array} child nodes - sorted list.
101
- */
102
- function sortedTemplateElements(node) {
103
- return [...node.quasis, ...node.expressions].sort(
104
- (a, b) => a.range[0] - b.range[0]
105
- );
106
- }
107
-
108
- /**
109
- * Replaces place holders with asterisk and returns the resulting id.
110
- *
111
- * @param {Object} node - parent node containing the locale id.
112
- * @returns {string} id - fixed id.
113
- */
114
- function templateLiteralDisplayStr(node) {
115
- return sortedTemplateElements(node)
116
- .map(e => (!e.value ? '*' : e.value.raw))
117
- .join('');
118
- }
119
-
120
- module.exports = {
121
- findFormatMessageAttrNode,
122
- findFormattedMessageAttrNode,
123
- findAttrNodeInDefineMessages,
124
- findAttrNodeInDefineMessage,
125
- sortedTemplateElements,
126
- templateLiteralDisplayStr
127
- };
@@ -1,14 +0,0 @@
1
- /**
2
- * Get a setting from eslint config
3
- *
4
- * @param {object} context - Context
5
- * @param {string} name - Name
6
- * @returns {any} result
7
- */
8
- function getSetting(context, name) {
9
- return context.settings[`agilebot/${name}`];
10
- }
11
-
12
- module.exports = {
13
- getSetting
14
- };
@@ -1,66 +0,0 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
- const { tsImport } = require('@agilebot/eslint-utils');
4
- const { getSetting } = require('./settings');
5
-
6
- /**
7
- * Map of locale file paths to keys and modified time
8
- *
9
- * @type {{string: {keys: Array, mtime: number}}}
10
- */
11
- const localeFilesKeys = {};
12
-
13
- /**
14
- * Get a list of ids keys from reading locale files
15
- * Keeps track of modified times and reloads if changed,; useful for realtime eslint in-editor
16
- *
17
- * @param {object} context - Context
18
- * @returns {string[]} results - Array of ids
19
- */
20
- function getIntlIds(context) {
21
- const projectRoot = getSetting(context, 'project-root');
22
- const localeFiles = getSetting(context, 'locale-files');
23
-
24
- if (!localeFiles) {
25
- throw new Error('localeFiles not in settings');
26
- }
27
-
28
- const results = [];
29
- localeFiles.forEach(f => {
30
- const fullPath = projectRoot ? path.join(projectRoot, f) : f;
31
- const mtime = fs.lstatSync(fullPath).mtime.getTime();
32
- if (
33
- !localeFilesKeys[fullPath] ||
34
- mtime !== localeFilesKeys[fullPath].mtime
35
- ) {
36
- let json;
37
- if (fullPath.endsWith('.json')) {
38
- json = JSON.parse(fs.readFileSync(fullPath));
39
- } else if (fullPath.endsWith('.ts')) {
40
- json = tsImport(fullPath);
41
- if (typeof json === 'object' && json.default) {
42
- json = json.default;
43
- }
44
- } else if (fullPath.endsWith('.js')) {
45
- json = require(fullPath);
46
- if (typeof json === 'object' && json.default) {
47
- json = json.default;
48
- }
49
- } else {
50
- throw new Error('unsupported file extension');
51
- }
52
-
53
- localeFilesKeys[fullPath] = {
54
- keys: Object.keys(json),
55
- mtime: mtime
56
- };
57
- }
58
- results.push(...localeFilesKeys[fullPath].keys);
59
- });
60
-
61
- return results;
62
- }
63
-
64
- module.exports = {
65
- getIntlIds
66
- };