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:
Geido 2022-03-28 16:11:34 +03:00 committed by GitHub
parent d46a550774
commit 6b9113a17b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 358 additions and 1 deletions

View File

@ -67,7 +67,7 @@ module.exports = {
version: 'detect',
},
},
plugins: ['prettier', 'react', 'file-progress'],
plugins: ['prettier', 'react', 'file-progress', 'theme-colors'],
overrides: [
{
files: ['*.ts', '*.tsx'],
@ -181,10 +181,28 @@ module.exports = {
],
'no-only-tests/no-only-tests': 'error',
'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: {
'theme-colors/no-literal-colors': 1,
camelcase: [
'error',
{

View File

@ -232,6 +232,7 @@
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^3.10.1",
"eslint-plugin-theme-colors": "file:tools/eslint-plugin-theme-colors",
"exports-loader": "^0.7.0",
"fetch-mock": "^7.7.3",
"fork-ts-checker-webpack-plugin": "^6.3.3",
@ -277,6 +278,18 @@
"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": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
@ -33122,6 +33135,10 @@
"node": ">=10"
}
},
"node_modules/eslint-plugin-theme-colors": {
"resolved": "tools/eslint-plugin-theme-colors",
"link": true
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -59990,6 +60007,18 @@
"src": {
"version": "0.0.1",
"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": {
@ -86194,6 +86223,12 @@
}
}
},
"eslint-plugin-theme-colors": {
"version": "file:tools/eslint-plugin-theme-colors",
"requires": {
"lodash": "^4.17.21"
}
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",

View File

@ -292,6 +292,7 @@
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^3.10.1",
"eslint-plugin-theme-colors": "file:tools/eslint-plugin-theme-colors",
"exports-loader": "^0.7.0",
"fetch-mock": "^7.7.3",
"fork-ts-checker-webpack-plugin": "^6.3.3",

View File

@ -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',
];

View File

@ -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);
}
},
};
},
},
},
};

View File

@ -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"
}
}