Skip to content

Commit 4fa599c

Browse files
committed
[Lint] add custom stylelint rules and config
Adding `@osd/stylelint-config` and `@osd/stylelint-plugin-stylelint` packages. These packages are utilized by OSD core and can be ran with the following: `yarn lint:style` Can be used to fix known non-compliant styling with the following: `yarn lint:style --fix` Can be used to audit untracked styling (based on defined rules) with the following: ``` export OUI_AUDIT_ENABLED=true yarn lint:style ``` --- `@osd/stylelint-config` Defines rules approved by UX and OSD core in JSON files and is added to OSD core. Within this commit is defined `colors.json` and `global_selectors.json`. `colors.json` defines a property that can be matched with a regex of a selector. If the selector is tracked it will have an `approved` value and a list of `rejected` values that UX knows if a value should be something. `global_selectors.json` defines a selector that if tracked, it will have an `approved` list of relative paths to files that can modify the global selector. --- `@osd/stylelint-plugin-stylelint` Creates the functionality that utilizes the JSON files within the `@osd/stylelint-config`. Within this commit is defined `no_custom_colors` and `no_modifying_global_selectors` rules. `no_custom_colors` checks if a property is a color property. It then utilizes a compliance engine helper to check the `colors.json` to see if the property being modified has a compliance rule available for the property for the specific selector and if it is not compliant. For example, if a selector matches `button` and we are trying to apply `background-color: red` to it. Stylelint will catch this and flag this as a known non-compliance issue since it knows that it should `$euiColorWarning`. If we pass `--fix` the property will be updated to be `$euiColorWarning`. If `OUI_AUDIT_ENABLED` is true it will catch all `background-color` being modified that is not being defined explicitly in `colors.json` `no_modifying_global_selectors` checks if a selector being modified is defined in `global_selectors.json` to see if a selector not defined in a specific list of approved files. For example, if a selector matches `#opensearch-dashboards-body` and it is being modified in `src/core/public/rendering/_base.scss`. Stylelint will catch this and flag this as a non-compliance issue. Since no other file should be modifying this selector. If we pass `--fix` the styling will be complete removed from the non-compliant file. --- Next steps: * Migrate these packages to OUI * Consider adding `yarn lint:style --fix` to the build release script here: https://github.com/opensearch-project/opensearch-build/blob/main/scripts/default/opensearch-dashboards/build.sh#L89 Issue: opensearch-project#4246 Signed-off-by: Kawika Avilla <kavilla414@gmail.com>
1 parent 2322c53 commit 4fa599c

22 files changed

+576
-0
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/.chromium
44
/build
55
/built_assets
6+
/bwc_tmp
67
/config/apm.dev.js
78
/data
89
/html_docs

.stylelintrc.yml

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
extends:
22
- stylelint-config-standard-scss
3+
- '@osd/stylelint-config'
34
rules:
45
# while we still use node-sass, only legacy rgb() notation is allowed
56
color-function-notation: "legacy"

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@
248248
"@osd/optimizer": "1.0.0",
249249
"@osd/plugin-generator": "1.0.0",
250250
"@osd/pm": "1.0.0",
251+
"@osd/stylelint-config": "1.0.0",
252+
"@osd/stylelint-plugin-stylelint": "1.0.0",
251253
"@osd/telemetry-tools": "1.0.0",
252254
"@osd/test": "1.0.0",
253255
"@osd/test-subj-selector": "0.2.1",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Any modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
module.exports = {
13+
plugins: [
14+
'@osd/stylelint-plugin-stylelint',
15+
],
16+
17+
rules: {
18+
'@osd/stylelint/no_modifying_global_selectors': [
19+
{
20+
config: "./../../../osd-stylelint-config/config/global_selectors.json"
21+
},
22+
{
23+
severity: "error"
24+
}
25+
],
26+
'@osd/stylelint/no_custom_colors': [
27+
{
28+
config: './../../../osd-stylelint-config/config/colors.json'
29+
},
30+
]
31+
}
32+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# osd-stylelint-config
2+
3+
The Stylelint config used by OpenSearch Dashboards
4+
5+
## Usage
6+
7+
To use this stylelint config, just install the peer dependencies and reference it
8+
in your `.stylelintrc`:
9+
10+
```javascript
11+
{
12+
extends: [
13+
'@osd/stylelint-config'
14+
]
15+
}
16+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"background-color": {
3+
"button": [
4+
{
5+
"approved": "$euiColorPrimary",
6+
"rejected": [
7+
"blue"
8+
]
9+
},
10+
{
11+
"approved": "$euiColorWarning",
12+
"rejected": [
13+
"red"
14+
]
15+
}
16+
]
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"#opensearch-dashboards-body": {
3+
"approved": [
4+
"src/core/public/rendering/_base.scss"
5+
]
6+
},
7+
".app-wrapper": {
8+
"approved": [
9+
"src/core/public/rendering/_base.scss"
10+
]
11+
}
12+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@osd/stylelint-config",
3+
"version": "1.0.0",
4+
"description": "The stylelint config used by OpenSearch Dashboards",
5+
"main": ".stylelintrc.js",
6+
"private": true,
7+
"license": "Apache-2.0",
8+
"opensearchDashboards": {
9+
"devOnly": true
10+
},
11+
"peerDependencies": {
12+
"@osd/stylelint-plugin-stylelint": "1.0.0",
13+
"stylelint": "^14.5.2"
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Custom Stylelint rules for OpenSearch Dashboards
2+
3+
This package contains custom Stylelint rules used for OpenSearch Dashboards development.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@osd/stylelint-plugin-stylelint",
3+
"main": "./target/index.js",
4+
"types": "./target/index.d.ts",
5+
"version": "1.0.0",
6+
"private": true,
7+
"license": "Apache-2.0",
8+
"scripts": {
9+
"build": "tsc",
10+
"osd:bootstrap": "yarn build"
11+
},
12+
"opensearchDashboards": {
13+
"devOnly": true
14+
},
15+
"peerDependencies": {
16+
"stylelint": "^14.5.2"
17+
},
18+
"devDependencies": {
19+
"typescript": "4.0.2",
20+
"tsd": "^0.21.0"
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Any modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
import stylelint from 'stylelint';
13+
import rules from './rules';
14+
15+
export const NAMESPACE = '@osd/stylelint';
16+
17+
const rulesPlugins = Object.keys(rules).map((ruleName: string) => {
18+
return stylelint.createPlugin(`${NAMESPACE}/${ruleName}`, rules[ruleName]);
19+
});
20+
21+
// eslint-disable-next-line import/no-default-export
22+
export default rulesPlugins;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Any modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
import noCustomColors from './no_custom_colors';
13+
import noModifyingGlobalSelectors from './no_modifying_global_selectors';
14+
15+
// eslint-disable-next-line import/no-default-export
16+
export default {
17+
no_custom_colors: noCustomColors,
18+
no_modifying_global_selectors: noModifyingGlobalSelectors,
19+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Any modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
import stylelint from 'stylelint';
13+
import { NAMESPACE } from '../..';
14+
import {
15+
ComplianceEngine,
16+
getTrackedMessage,
17+
getUntrackedMessage,
18+
getNotCompliantMessage,
19+
getRulesFromConfig,
20+
isColorProperty,
21+
isValidOptions,
22+
} from '../../utils';
23+
24+
const isOuiAuditEnabled = Boolean(process.env.OUI_AUDIT_ENABLED);
25+
26+
const { ruleMessages, report } = stylelint.utils;
27+
const engine = ComplianceEngine.default;
28+
29+
const ruleName = 'no_custom_colors';
30+
const messages = ruleMessages(ruleName, {
31+
expected: (message) => `${message}`,
32+
});
33+
34+
const ruleFunction = (
35+
primaryOption: Record<string, any>,
36+
secondaryOptionObject: Record<string, any>,
37+
context
38+
) => {
39+
return (postcssRoot: any, postcssResult: any) => {
40+
const validOptions = isValidOptions(postcssResult, ruleName, primaryOption);
41+
if (!validOptions) {
42+
return;
43+
}
44+
45+
const rules = getRulesFromConfig(primaryOption.config);
46+
47+
const isAutoFixing = Boolean(context.fix);
48+
49+
postcssRoot.walkDecls((decl: any) => {
50+
if (!isColorProperty(decl.prop)) {
51+
return;
52+
}
53+
54+
let shouldReport = false;
55+
56+
const nodeInfo = {
57+
selector: decl.parent.selector,
58+
prop: decl.prop,
59+
value: decl.value,
60+
};
61+
62+
const reportInfo = {
63+
ruleName: `${NAMESPACE}/${ruleName}`,
64+
result: postcssResult,
65+
node: decl,
66+
message: '',
67+
};
68+
69+
if (isOuiAuditEnabled && !engine.isTracked(rules, nodeInfo)) {
70+
reportInfo.message = messages.expected(getUntrackedMessage(nodeInfo));
71+
report(reportInfo);
72+
return;
73+
}
74+
75+
const ruleObject = engine.getComplianceRule(rules, nodeInfo);
76+
77+
if (!ruleObject) {
78+
if (isOuiAuditEnabled) {
79+
reportInfo.message = messages.expected(getTrackedMessage(nodeInfo));
80+
report(reportInfo);
81+
}
82+
return;
83+
}
84+
85+
shouldReport = !ruleObject.isComplaint;
86+
87+
if (shouldReport && isAutoFixing) {
88+
decl.value = ruleObject.expected;
89+
return;
90+
}
91+
92+
if (!shouldReport) {
93+
return;
94+
}
95+
96+
reportInfo.message = messages.expected(getNotCompliantMessage(ruleObject.message));
97+
report(reportInfo);
98+
});
99+
};
100+
};
101+
102+
ruleFunction.ruleName = ruleName;
103+
ruleFunction.messages = messages;
104+
105+
// eslint-disable-next-line import/no-default-export
106+
export default ruleFunction;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Any modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
import stylelint from 'stylelint';
13+
import { NAMESPACE } from '../..';
14+
import { getNotCompliantMessage, getRulesFromConfig, isValidOptions } from '../../utils';
15+
16+
const { ruleMessages, report } = stylelint.utils;
17+
18+
const ruleName = 'no_modifying_global_selectors';
19+
const messages = ruleMessages(ruleName, {
20+
expected: (message) => `${message}`,
21+
});
22+
23+
const ruleFunction = (
24+
primaryOption: Record<string, any>,
25+
secondaryOptionObject: Record<string, any>,
26+
context
27+
) => {
28+
return (postcssRoot: any, postcssResult: any) => {
29+
const validOptions = isValidOptions(postcssResult, ruleName, primaryOption);
30+
if (!validOptions) {
31+
return;
32+
}
33+
34+
const rules = getRulesFromConfig(primaryOption.config);
35+
36+
const isAutoFixing = Boolean(context.fix);
37+
38+
postcssRoot.walkRules((rule: any) => {
39+
const selectorRule = rules[rule.selector];
40+
if (!selectorRule) {
41+
return;
42+
}
43+
44+
let shouldReport = false;
45+
46+
const file = postcssRoot.source.input.file;
47+
const approvedFiles = selectorRule.approved;
48+
49+
const reportInfo = {
50+
ruleName: `${NAMESPACE}/${ruleName}`,
51+
result: postcssResult,
52+
node: rule,
53+
message: '',
54+
};
55+
56+
if (approvedFiles) {
57+
shouldReport = !approvedFiles.some((inspectedFile) => {
58+
return file.includes(inspectedFile);
59+
});
60+
}
61+
62+
if (shouldReport && isAutoFixing) {
63+
rule.remove();
64+
return;
65+
}
66+
67+
if (!shouldReport) {
68+
return;
69+
}
70+
71+
reportInfo.message = messages.expected(
72+
getNotCompliantMessage(`Modifying global selector "${rule.selector}" not allowed.`)
73+
);
74+
report(reportInfo);
75+
});
76+
};
77+
};
78+
79+
ruleFunction.ruleName = ruleName;
80+
ruleFunction.messages = messages;
81+
82+
// eslint-disable-next-line import/no-default-export
83+
export default ruleFunction;

0 commit comments

Comments
 (0)