Skip to content

Commit

Permalink
feat(stylelint-plugin-meteor): add no primitive token rule
Browse files Browse the repository at this point in the history
  • Loading branch information
Haberkamp committed Dec 4, 2024
1 parent 22d47f6 commit 02ecfaf
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/sour-chicken-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-ag/stylelint-plugin-meteor": minor
---

Add no-primitive-token rule
3 changes: 2 additions & 1 deletion packages/component-library/.stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"rules": {
"meteor/prefer-sizing-token": [true, { "severity": "warning" }],
"meteor/prefer-background-token": [true, { "severity": "warning" }],
"meteor/prefer-color-token": [true, { "severity": "warning" }]
"meteor/prefer-color-token": [true, { "severity": "warning" }],
"meteor/no-primitive-token": [true, { "severity": "warning" }]
},
"overrides": [
{
Expand Down
3 changes: 2 additions & 1 deletion packages/stylelint-plugin-meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
"license": "MIT",
"description": "The stylelint plugin for the Meteor Design System",
"peerDependencies": {
"@shopware-ag/meteor-tokens": "workspace:*",
"stylelint": "^16.10.0"
},
"devDependencies": {
"tshy": "^3.0.2",
"stylelint-test-rule-node": "^0.3.0",
"tshy": "^3.0.2",
"tsx": "^4.7.0"
},
"type": "module",
Expand Down
8 changes: 7 additions & 1 deletion packages/stylelint-plugin-meteor/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import preferSizingTokens from "./rules/prefer-sizing-token/index.js";
import preferBackgroundToken from "./rules/prefer-background-token/index.js";
import preferColorToken from "./prefer-color-token/index.js";
import noPrimitiveToken from "./rules/no-primitive-token/index.js";

export default [preferSizingTokens, preferColorToken, preferBackgroundToken];
export default [
preferSizingTokens,
preferColorToken,
preferBackgroundToken,
noPrimitiveToken,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface TokenGateway {
getTokens(): Promise<string[]>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import stylelint, { Rule } from "stylelint";
import { TokenGateway } from "./application/TokenGateway.js";
import { TokenGatewayUsingImport } from "./infrastructure/TokenGatewayUsingImport.js";

const {
createPlugin,
utils: { report, ruleMessages, validateOptions },
} = stylelint;

const ruleName = "meteor/no-primitive-token";

const messages = ruleMessages(ruleName, {
rejected: (token, property) =>
`Unexpected primitve token "${token}" for ${property}, use a semantic token instead`,
});

const meta = {
url: "",
};

export function makeNoPrimitiveRule(dependencies: {
tokenGateway: TokenGateway;
}) {
const ruleFunction: Rule = (primary) => {
return async (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: primary,
possible: [true],
});

if (!validOptions) return;

const tokens = await dependencies.tokenGateway.getTokens();

root.walkDecls((ruleNode) => {
const values = ruleNode.value.split(" ");

values.forEach((value) => {
const usingACustomProperty = /^var\(.*\)$/.test(value);

if (!usingACustomProperty) return;

const token = value.replace(/^var\(--/, "").replace(/\)$/, "");
const usingAPrimitiveToken = tokens.includes(token);
const isScaleToken = /^scale\-size\-\d+$/.test(token);

if (usingAPrimitiveToken && !isScaleToken) {
report({
message: messages.rejected(token, ruleNode.prop),
node: ruleNode,
result,
ruleName,
});
}
});
});
};
};

ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
ruleFunction.meta = meta;

return ruleFunction;
}

export default createPlugin(
ruleName,
makeNoPrimitiveRule({
tokenGateway: new TokenGatewayUsingImport(),
})
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TokenGateway } from "../application/TokenGateway.js";

export class InMemoryTokenGateway implements TokenGateway {
constructor(public tokens: string[]) {}

async getTokens(): Promise<string[]> {
return this.tokens;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { TokenGateway } from "../application/TokenGateway.js";

const extractTokens = (obj: any, path: string[] = []): string[] => {
let tokens: string[] = [];

for (const [key, value] of Object.entries(obj)) {
if (typeof value === "object" && value !== null && !("$value" in value)) {
tokens = tokens.concat(extractTokens(value, [...path, key]));
} else if (
typeof value === "object" &&
value !== null &&
"$value" in value
) {
tokens.push([...path, key].join("-"));
}
}

return tokens;
};

export class TokenGatewayUsingImport implements TokenGateway {
async getTokens(): Promise<string[]> {
const dictionary = await import(
"@shopware-ag/meteor-tokens/foundation/primitives.json",
{ with: { type: "json" } }
);

return extractTokens(dictionary.default);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { testRule } from "stylelint-test-rule-node";
import stylelint from "stylelint";
import { makeNoPrimitiveRule } from "./index.js";
import { InMemoryTokenGateway } from "./infrastructure/InMemoryTokenGateway.js";

const { createPlugin } = stylelint;

const rule = makeNoPrimitiveRule({
tokenGateway: new InMemoryTokenGateway([
"green-500",
"slate-300",
"scale-size-4",
]),
});

const plugin = createPlugin(rule.ruleName, rule);

testRule({
plugins: [plugin],
ruleName: rule.ruleName,
config: true,

accept: [
{
code: "a { background-color: var(--color-interaction-primary-default); }",
},
{
code: "a { background-color: currentcolor; }",
},
{
code: "a { background-color: unset; }",
},
{
code: "a { background-color: initial; }",
},
{
code: "a { background-color: transparent; }",
},
{
code: "a { background-color: red; }",
},
{
code: "a { background-color: #fff; }",
},
{
code: "a { background-color: rgb(1, 56, 56); }",
},
{
code: "a { background-color: hsl(254, 45%, 60%); }",
},
{
code: "a { background-color: var(--some-custom-property); }",
},
{
code: "a { background-color: var(--scale-size-4); }",
},
{
code: "a { margin: var(--scale-size-4) var(--scale-size-8); }",
},
{
code: "a { border: 1px solid var(--color-border-brand-selected); }",
},
{
code: "a { --foo: var(--scale-size-4); }",
},
],

reject: [
{
code: "a { background: var(--green-500); }",
message:
'Unexpected primitve token "green-500" for background, use a semantic token instead (meteor/no-primitive-token)',
line: 1,
endLine: 1,
column: 5,
endColumn: 34,
},
{
code: "a { color: var(--slate-300); }",
message:
'Unexpected primitve token "slate-300" for color, use a semantic token instead (meteor/no-primitive-token)',
line: 1,
endLine: 1,
column: 5,
endColumn: 29,
},
{
code: "a { border: 1px solid var(--green-500); }",
message:
'Unexpected primitve token "green-500" for border, use a semantic token instead (meteor/no-primitive-token)',
line: 1,
endLine: 1,
column: 5,
endColumn: 40,
},
{
code: "a { margin: var(--scale-size-4) var(--green-500); }",
message:
'Unexpected primitve token "green-500" for margin, use a semantic token instead (meteor/no-primitive-token)',
line: 1,
endLine: 1,
column: 5,
endColumn: 50,
},
{
code: "a { padding: var(--slate-300) var(--green-500); }",
warnings: [
{
message:
'Unexpected primitve token "slate-300" for padding, use a semantic token instead (meteor/no-primitive-token)',
line: 1,
column: 5,
endLine: 1,
endColumn: 48,
},
{
message:
'Unexpected primitve token "green-500" for padding, use a semantic token instead (meteor/no-primitive-token)',
line: 1,
column: 5,
endLine: 1,
endColumn: 48,
},
],
},
{
code: "a { --foo: var(--green-500); }",
message:
'Unexpected primitve token "green-500" for --foo, use a semantic token instead (meteor/no-primitive-token)',
line: 1,
endLine: 1,
column: 5,
endColumn: 29,
},
],
});
3 changes: 2 additions & 1 deletion packages/tokens/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"exports": {
"./primitives.css": "./deliverables/foundation/primitives.css",
"./administration/light.css": "./deliverables/administration/light.css",
"./administration/dark.css": "./deliverables/administration/dark.css"
"./administration/dark.css": "./deliverables/administration/dark.css",
"./foundation/primitives.json": "./dictionaries/foundation/primitives.tokens.json"
},
"main": "index.js",
"files": [
Expand Down
Loading

0 comments on commit 02ecfaf

Please sign in to comment.