chore: Eslint custom plugin to warn about hex and literal colors (#19239)
* wip * Add eslint custom plugin * Refactor * Clean up * Update superset-frontend/buildtools/eslint-plugin-theme-colors/index.js Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com> * Refactor * Update superset-frontend/buildtools/eslint-plugin-theme-colors/index.js Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com> * Clean up Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
This commit is contained in:
parent
d46a550774
commit
6b9113a17b
|
|
@ -67,7 +67,7 @@ module.exports = {
|
||||||
version: 'detect',
|
version: 'detect',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: ['prettier', 'react', 'file-progress'],
|
plugins: ['prettier', 'react', 'file-progress', 'theme-colors'],
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['*.ts', '*.tsx'],
|
files: ['*.ts', '*.tsx'],
|
||||||
|
|
@ -181,10 +181,28 @@ module.exports = {
|
||||||
],
|
],
|
||||||
'no-only-tests/no-only-tests': 'error',
|
'no-only-tests/no-only-tests': 'error',
|
||||||
'max-classes-per-file': 0,
|
'max-classes-per-file': 0,
|
||||||
|
'theme-colors/no-literal-colors': 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'*.test.ts',
|
||||||
|
'*.test.tsx',
|
||||||
|
'*.test.js',
|
||||||
|
'*.test.jsx',
|
||||||
|
'*.stories.tsx',
|
||||||
|
'*.stories.jsx',
|
||||||
|
'fixtures.*',
|
||||||
|
'cypress-base/cypress/**/*',
|
||||||
|
'Stories.tsx',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'theme-colors/no-literal-colors': 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
'theme-colors/no-literal-colors': 1,
|
||||||
camelcase: [
|
camelcase: [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,7 @@
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"eslint-plugin-testing-library": "^3.10.1",
|
"eslint-plugin-testing-library": "^3.10.1",
|
||||||
|
"eslint-plugin-theme-colors": "file:tools/eslint-plugin-theme-colors",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "^0.7.0",
|
||||||
"fetch-mock": "^7.7.3",
|
"fetch-mock": "^7.7.3",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.3.3",
|
"fork-ts-checker-webpack-plugin": "^6.3.3",
|
||||||
|
|
@ -277,6 +278,18 @@
|
||||||
"npm": "^7.5.4"
|
"npm": "^7.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"buildtools/eslint-plugin-theme-colors": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"extraneous": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.9.1",
|
||||||
|
"npm": "^7.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
||||||
|
|
@ -33122,6 +33135,10 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-theme-colors": {
|
||||||
|
"resolved": "tools/eslint-plugin-theme-colors",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/eslint-scope": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
|
|
@ -59990,6 +60007,18 @@
|
||||||
"src": {
|
"src": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"extraneous": true
|
"extraneous": true
|
||||||
|
},
|
||||||
|
"tools/eslint-plugin-theme-colors": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.9.1",
|
||||||
|
"npm": "^7.5.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -86194,6 +86223,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"eslint-plugin-theme-colors": {
|
||||||
|
"version": "file:tools/eslint-plugin-theme-colors",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,7 @@
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"eslint-plugin-testing-library": "^3.10.1",
|
"eslint-plugin-testing-library": "^3.10.1",
|
||||||
|
"eslint-plugin-theme-colors": "file:tools/eslint-plugin-theme-colors",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "^0.7.0",
|
||||||
"fetch-mock": "^7.7.3",
|
"fetch-mock": "^7.7.3",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.3.3",
|
"fork-ts-checker-webpack-plugin": "^6.3.3",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// https://www.w3.org/wiki/CSS/Properties/color/keywords
|
||||||
|
module.exports = [
|
||||||
|
'black',
|
||||||
|
'silver',
|
||||||
|
'gray',
|
||||||
|
'grey',
|
||||||
|
'white',
|
||||||
|
'maroon',
|
||||||
|
'red',
|
||||||
|
'purple',
|
||||||
|
'fuchsia',
|
||||||
|
'green',
|
||||||
|
'lime',
|
||||||
|
'olive',
|
||||||
|
'yellow',
|
||||||
|
'navy',
|
||||||
|
'blue',
|
||||||
|
'teal',
|
||||||
|
'aqua',
|
||||||
|
'aliceblue',
|
||||||
|
'antiquewhite',
|
||||||
|
'aquamarine',
|
||||||
|
'azure',
|
||||||
|
'beige',
|
||||||
|
'bisque',
|
||||||
|
'blanchedalmond',
|
||||||
|
'blueviolet',
|
||||||
|
'brown',
|
||||||
|
'burlywood',
|
||||||
|
'cadetblue',
|
||||||
|
'chartreuse',
|
||||||
|
'chocolate',
|
||||||
|
'coral',
|
||||||
|
'cornflowerblue',
|
||||||
|
'cornsilk',
|
||||||
|
'crimson',
|
||||||
|
'cyan',
|
||||||
|
'darkblue',
|
||||||
|
'darkcyan',
|
||||||
|
'darkgoldenrod',
|
||||||
|
'darkgray',
|
||||||
|
'darkgreen',
|
||||||
|
'darkgrey',
|
||||||
|
'darkkhaki',
|
||||||
|
'darkmagenta',
|
||||||
|
'darkolivegreen',
|
||||||
|
'darkorange',
|
||||||
|
'darkorchid',
|
||||||
|
'darkred',
|
||||||
|
'darksalmon',
|
||||||
|
'darkseagreen',
|
||||||
|
'darkslateblue',
|
||||||
|
'darkslategray',
|
||||||
|
'darkslategrey',
|
||||||
|
'darkturquoise',
|
||||||
|
'darkviolet',
|
||||||
|
'deeppink',
|
||||||
|
'deepskyblue',
|
||||||
|
'dimgray',
|
||||||
|
'dimgrey',
|
||||||
|
'dodgerblue',
|
||||||
|
'firebrick',
|
||||||
|
'floralwhite',
|
||||||
|
'forestgreen',
|
||||||
|
'fuchsia',
|
||||||
|
'gainsboro',
|
||||||
|
'ghostwhite',
|
||||||
|
'gold',
|
||||||
|
'goldenrod',
|
||||||
|
'greenyellow',
|
||||||
|
'honeydew',
|
||||||
|
'hotpink',
|
||||||
|
'indianred',
|
||||||
|
'indigo',
|
||||||
|
'ivory',
|
||||||
|
'khaki',
|
||||||
|
'lavender',
|
||||||
|
'lavenderblush',
|
||||||
|
'lawngreen',
|
||||||
|
'lemonchiffon',
|
||||||
|
'lightblue',
|
||||||
|
'lightcoral',
|
||||||
|
'lightcyan',
|
||||||
|
'lightgoldenrodyellow',
|
||||||
|
'lightgray',
|
||||||
|
'lightgreen',
|
||||||
|
'lightgrey',
|
||||||
|
'lightpink',
|
||||||
|
'lightsalmon',
|
||||||
|
'lightseagreen',
|
||||||
|
'lightskyblue',
|
||||||
|
'lightslategray',
|
||||||
|
'lightslategrey',
|
||||||
|
'lightsteelblue',
|
||||||
|
'lightyellow',
|
||||||
|
'limegreen',
|
||||||
|
'linen',
|
||||||
|
'magenta',
|
||||||
|
'maroon',
|
||||||
|
'mediumaquamarine',
|
||||||
|
'mediumblue',
|
||||||
|
'mediumorchid',
|
||||||
|
'mediumpurple',
|
||||||
|
'mediumseagreen',
|
||||||
|
'mediumslateblue',
|
||||||
|
'mediumspringgreen',
|
||||||
|
'mediumturquoise',
|
||||||
|
'mediumvioletred',
|
||||||
|
'midnightblue',
|
||||||
|
'mintcream',
|
||||||
|
'mistyrose',
|
||||||
|
'moccasin',
|
||||||
|
'navajowhite',
|
||||||
|
'oldlace',
|
||||||
|
'olivedrab',
|
||||||
|
'orange',
|
||||||
|
'orangered',
|
||||||
|
'orchid',
|
||||||
|
'palegoldenrod',
|
||||||
|
'palegreen',
|
||||||
|
'paleturquoise',
|
||||||
|
'palevioletred',
|
||||||
|
'papayawhip',
|
||||||
|
'peachpuff',
|
||||||
|
'peru',
|
||||||
|
'pink',
|
||||||
|
'plum',
|
||||||
|
'powderblue',
|
||||||
|
'rosybrown',
|
||||||
|
'royalblue',
|
||||||
|
'saddlebrown',
|
||||||
|
'salmon',
|
||||||
|
'sandybrown',
|
||||||
|
'seagreen',
|
||||||
|
'seashell',
|
||||||
|
'sienna',
|
||||||
|
'skyblue',
|
||||||
|
'slateblue',
|
||||||
|
'slategray',
|
||||||
|
'slategrey',
|
||||||
|
'snow',
|
||||||
|
'springgreen',
|
||||||
|
'steelblue',
|
||||||
|
'tan',
|
||||||
|
'teal',
|
||||||
|
'thistle',
|
||||||
|
'tomato',
|
||||||
|
'turquoise',
|
||||||
|
'violet',
|
||||||
|
'wheat',
|
||||||
|
'whitesmoke',
|
||||||
|
'yellowgreen',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview Rule to warn about literal colors
|
||||||
|
* @author Apache
|
||||||
|
*/
|
||||||
|
|
||||||
|
const COLOR_KEYWORDS = require('./colors');
|
||||||
|
|
||||||
|
function hasHexColor(quasi) {
|
||||||
|
if (typeof quasi === 'string') {
|
||||||
|
const regex = /#([a-f0-9]{3}|[a-f0-9]{4}(?:[a-f0-9]{2}){0,2})\b/gi;
|
||||||
|
return !!quasi.match(regex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasRgbColor(quasi) {
|
||||||
|
if (typeof quasi === 'string') {
|
||||||
|
const regex = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/i;
|
||||||
|
return !!quasi.match(regex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasLiteralColor(quasi, strict = false) {
|
||||||
|
if (typeof quasi === 'string') {
|
||||||
|
// matches literal colors at the start or end of a CSS prop
|
||||||
|
return COLOR_KEYWORDS.some(color => {
|
||||||
|
const regexColon = new RegExp(`: ${color}`);
|
||||||
|
const regexSemicolon = new RegExp(` ${color};`);
|
||||||
|
return (
|
||||||
|
!!quasi.match(regexColon) ||
|
||||||
|
!!quasi.match(regexSemicolon) ||
|
||||||
|
(strict && quasi === color)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WARNING_MESSAGE =
|
||||||
|
'Theme color variables are preferred over rgb(a)/hex/literal colors';
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Rule Definition
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** @type {import('eslint').Rule.RuleModule} */
|
||||||
|
module.exports = {
|
||||||
|
rules: {
|
||||||
|
'no-literal-colors': {
|
||||||
|
create(context) {
|
||||||
|
const warned = [];
|
||||||
|
return {
|
||||||
|
TemplateElement(node) {
|
||||||
|
const rawValue = node?.value?.raw;
|
||||||
|
const isParentProperty =
|
||||||
|
node?.parent?.parent?.type === 'TaggedTemplateExpression';
|
||||||
|
const loc = node?.parent?.parent?.loc;
|
||||||
|
const locId = loc && JSON.stringify(loc);
|
||||||
|
const hasWarned = warned.includes(locId);
|
||||||
|
if (
|
||||||
|
!hasWarned &&
|
||||||
|
isParentProperty &&
|
||||||
|
rawValue &&
|
||||||
|
(hasLiteralColor(rawValue) ||
|
||||||
|
hasHexColor(rawValue) ||
|
||||||
|
hasRgbColor(rawValue))
|
||||||
|
) {
|
||||||
|
context.report(node, loc, WARNING_MESSAGE);
|
||||||
|
warned.push(locId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Literal(node) {
|
||||||
|
const value = node?.value;
|
||||||
|
const isParentProperty = node?.parent?.type === 'Property';
|
||||||
|
const locId = JSON.stringify(node.loc);
|
||||||
|
const hasWarned = warned.includes(locId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasWarned &&
|
||||||
|
isParentProperty &&
|
||||||
|
value &&
|
||||||
|
(hasLiteralColor(value, true) ||
|
||||||
|
hasHexColor(value) ||
|
||||||
|
hasRgbColor(value))
|
||||||
|
) {
|
||||||
|
context.report(node, node.loc, WARNING_MESSAGE);
|
||||||
|
warned.push(locId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "eslint-plugin-theme-colors",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Warns about rgb(a)/hex/literal colors",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"author": "Apache",
|
||||||
|
"dependencies": {},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.9.1",
|
||||||
|
"npm": "^7.5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue