Skip to content

Commit

Permalink
feat: upgrade stylelint use and plugins to support v16 (#2780)
Browse files Browse the repository at this point in the history
  • Loading branch information
castastrophe authored Jul 25, 2024
1 parent c0af9e9 commit 7fea737
Show file tree
Hide file tree
Showing 15 changed files with 810 additions and 461 deletions.
7 changes: 7 additions & 0 deletions .changeset/healthy-pens-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@spectrum-tools/stylelint-no-unknown-custom-properties": major
"@spectrum-tools/stylelint-no-unused-custom-properties": major
"@spectrum-tools/stylelint-no-missing-var": major
---

Upgrades packages to support Stylelint v16 and leveraging ESM. Drops support for v14 and v15 in this release.
47 changes: 47 additions & 0 deletions .github/workflows/development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jobs:
styles_modified_files: ${{ steps.changed-files.outputs.styles_modified_files }}
eslint_added_files: ${{ steps.changed-files.outputs.eslint_added_files }}
eslint_modified_files: ${{ steps.changed-files.outputs.eslint_modified_files }}
plugins_modified_files: ${{ steps.changed-files.outputs.plugins_modified_files }}
plugins_added_files: ${{ steps.changed-files.outputs.plugins_added_files }}
permissions:
pull-requests: read

Expand All @@ -100,6 +102,8 @@ jobs:
- components/*/themes/express.css
eslint:
- components/*/stories/*.js
plugins:
- plugins/**/*
# -------------------------------------------------------------
# Lint pre-compiled assets for consistency
Expand All @@ -116,6 +120,49 @@ jobs:
eslint_modified_files: ${{ needs.changed_files.outputs.eslint_modified_files }}
secrets: inherit

# -------------------------------------------------------------
# Run the test suites for the plugins if there are changes
# to any of the plugin files
# -------------------------------------------------------------
plugin_tests:
name: Plugin tests
runs-on: ubuntu-latest
needs: [changed_files]
if: ${{ needs.changed_files.outputs.plugins_added_files != '' || needs.changed_files.outputs.plugins_modified_files != '' }}
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Use Node LTS version
uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn

- name: Enable Corepack
run: corepack enable

## --- YARN CACHE --- ##
- name: Check for cached dependencies
continue-on-error: true
id: cache-dependencies
uses: actions/cache@v4
with:
path: |
.cache/yarn
node_modules
key: ubuntu-latest-node20-${{ hashFiles('yarn.lock') }}

## --- INSTALL --- ##
# If statement isn't needed here b/c yarn will leverage the cache if it exists
- name: Install dependencies
shell: bash
run: yarn install --immutable

## --- Run plugins test suites --- ##
- name: Run plugin tests
run: yarn test:plugins

# -------------------------------------------------------------
# RUN VISUAL REGRESSION TESTS --- #
# Run VRT on:
Expand Down
8 changes: 4 additions & 4 deletions components/combobox/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
--spectrum-combobox-border-color-invalid-focus-hover: var(--spectrum-negative-border-color-focus-hover);
--spectrum-combobox-border-color-invalid-key-focus: var(--spectrum-negative-border-color-key-focus);

/* Settings for nested Textfield component. */
/* @passthroughs start -- settings for nested Textfield component */
--mod-textfield-focus-indicator-gap: var(--mod-combobox-focus-indicator-gap, var(--spectrum-combobox-focus-indicator-gap));
--mod-textfield-focus-indicator-width: var(--mod-combobox-focus-indicator-thickness, var(--spectrum-combobox-focus-indicator-thickness));
--mod-textfield-focus-indicator-color: var(--mod-combobox-focus-indicator-color, var(--spectrum-combobox-focus-indicator-color));
Expand Down Expand Up @@ -81,13 +81,15 @@
--mod-textfield-border-color-invalid-keyboard-focus: var(--mod-combobox-border-color-invalid-key-focus, var(--spectrum-combobox-border-color-invalid-key-focus));

--mod-textfield-icon-color-invalid: var(--mod-combobox-alert-icon-color);
/* @passthroughs end -- settings for nested Textfield component */

/* Settings for nested Picker Button component. */
/* @passthroughs start -- settings for nested Picker Button component */
--mod-picker-button-border-width: var(--mod-combobox-border-width, var(--spectrum-combobox-border-width));
--mod-picker-button-border-color: var(--mod-combobox-border-color-default, var(--spectrum-combobox-border-color-default));
--mod-picker-button-background-color: var(--mod-combobox-background-color-default);
--mod-picker-button-background-color-disabled: var(--mod-combobox-background-color-disabled);
--mod-picker-button-font-color-disabled: var(--mod-combobox-font-color-disabled);
/* @passthroughs end -- settings for nested Picker Button component */
}

.spectrum-Combobox--sizeS {
Expand Down Expand Up @@ -227,7 +229,6 @@
}

/* PICKER BUTTON */
/* stylelint-disable max-nesting-depth */
.spectrum-Combobox-button {
position: absolute;
inset-inline-end: calc(-1 * var(--mod-combobox-button-inline-offset, var(--spectrum-combobox-button-inline-offset, 0px)));
Expand Down Expand Up @@ -296,7 +297,6 @@
}
}
}
/* stylelint-enable max-nesting-depth */

/* TEXTFIELD (wrapper) */
.spectrum-Combobox-textfield {
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@
"postcss-splitinator": "workspace:^",
"prettier": "^3.2.5",
"rimraf": "^6.0.1",
"stylelint": "^15.11.0",
"stylelint-config-clean-order": "^5.4.2",
"stylelint-config-standard": "^34.0.0",
"stylelint-header": "^1.0.0",
"stylelint": "^16.7.0",
"stylelint-config-clean-order": "^6.1.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-header": "^2.0.1",
"stylelint-high-performance-animation": "^1.10.0",
"stylelint-selector-bem-pattern": "^3.0.1",
"stylelint-selector-bem-pattern": "^4.0.0",
"stylelint-use-logical": "^2.1.2",
"tar": "^7.2.0",
"yargs": "^17.7.2"
Expand Down
13 changes: 8 additions & 5 deletions plugins/stylelint-no-missing-var/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
* governing permissions and limitations under the License.
*/

import stylelint from "stylelint";

const {
createPlugin,
utils: { report, ruleMessages, validateOptions }
} = require("stylelint");
} = stylelint;

require("colors");
import "colors";

const ruleName = "spectrum-tools/no-missing-var";
const messages = ruleMessages(ruleName, {
Expand Down Expand Up @@ -60,7 +62,8 @@ const ruleFunction = (enabled, _options, context) => {
};
};

module.exports.ruleName = ruleName;
module.exports.messages = messages;

module.exports = createPlugin(ruleName, ruleFunction);
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;

export default createPlugin(ruleName, ruleFunction);
9 changes: 6 additions & 3 deletions plugins/stylelint-no-missing-var/package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
{
"private": true,
"name": "@spectrum-tools/stylelint-no-missing-var",
"version": "1.3.2",
"description": "Check for custom properties without a var function around them",
"license": "Apache-2.0",
"author": "Adobe",
"type": "module",
"main": "index.js",
"scripts": {
"test": "ava"
},
"dependencies": {
"colors": "^1.4.0"
},
"peerDependencies": {
"stylelint": "^14.0.0 || ^15.0.0"
"stylelint": ">=16.0.0"
},
"devDependencies": {
"ava": "^6.1.3",
"c8": "^9.1.0",
"stylelint": "^15.11.0"
"stylelint": "^16.5.0"
},
"keywords": [
"css",
Expand Down
19 changes: 11 additions & 8 deletions plugins/stylelint-no-missing-var/test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
const fs = require("fs");
const { join } = require("path");
import { readFileSync } from "fs";
import { join } from "path";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));

const test = require("ava");
const stylelint = require("stylelint");
import test from "ava";
import stylelint from "stylelint";
const { lint } = stylelint;

const plugin = require("./index");
const { ruleName } = require("./index");
import plugin from "./index.js";
const { ruleName } = plugin;

async function compare(_, fixtureFilePath) {
const code = readFile(`./fixtures/${fixtureFilePath}`);
return stylelint.lint({
return lint({
code,
config: {
plugins: [plugin],
Expand All @@ -21,7 +24,7 @@ async function compare(_, fixtureFilePath) {
}

function readFile(filename) {
return fs.readFileSync(join(__dirname, filename), "utf8");
return readFileSync(join(__dirname, filename), "utf8");
}

test("should throw an error for missing \"var\" before custom properties", async (t) => {
Expand Down
79 changes: 39 additions & 40 deletions plugins/stylelint-no-unknown-custom-properties/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,29 @@
* governing permissions and limitations under the License.
*/

const path = require("path");
const fs = require("fs");
import { existsSync, readFileSync } from "fs";
import { createRequire } from "module";
import { join, sep } from "path";
const require = createRequire(import.meta.url);

import stylelint from "stylelint";
import { isBoolean, isRegExp, isString } from "stylelint/lib/utils/validateTypes.mjs";

const {
createPlugin,
utils: { report, ruleMessages, validateOptions }
} = require("stylelint");

const { isString, isRegExp, isBoolean } = require("stylelint/lib/utils/validateTypes");
} = stylelint;

require("colors");
import "colors";

const ruleName = "spectrum-tools/no-unknown-custom-properties";
const messages = ruleMessages(ruleName, {
expected: (prop) => `Custom property ${prop.magenta} not defined`,
});

const fg = require("fast-glob");
const postcss = require("postcss");
const valueParser = require("postcss-value-parser");
import fg from "fast-glob";
import { parse } from "postcss";
import valueParser from "postcss-value-parser";

/** @type {import('stylelint').Plugin} */
const ruleFunction = (enabled, options = {}) => {
Expand All @@ -55,82 +58,78 @@ const ruleFunction = (enabled, options = {}) => {

const { ignoreList = [], skipDependencies = true } = options;

/** @type {Set<string>} */
const localDefinitions = new Set();
root.walkDecls(/^--/, ({ prop }) => {
localDefinitions.add(prop);
});

const sourceFile = root.source.input.file;
const parts = sourceFile ? sourceFile.split(path.sep) : [];
const parts = sourceFile ? sourceFile.split(sep) : [];

// @todo this is a hard-coded assumption that the components directory is the root before the component name
const rootIdx = parts.indexOf("components");
const componentRoot = parts.slice(0, rootIdx + 2).join(path.sep);
const componentRoot = parts.slice(0, rootIdx + 2).join(sep);

// We should only be checking entry-points, i.e. index.css files
// because others are likely to be partials or imported into main
if (rootIdx && typeof sourceFile !== "undefined" && !sourceFile.endsWith("index.css")) return;

const sharedDefinitions = new Set();

for (const themePath of fg.sync(["themes/*.css"], { cwd: componentRoot, absolute: true })) {
const content = fs.readFileSync(themePath, "utf8");
const content = readFileSync(themePath, "utf8");
if (!content) continue;
postcss.parse(content).walkDecls(/^--/, ({ prop }) => {
parse(content).walkDecls(/^--/, ({ prop }) => {
sharedDefinitions.add(prop);
});
}

function fetchResolutions(depName) {
let req;
try {
req = require.resolve(depName, {
paths: [
path.join(componentRoot, "node_modules"), path.join(__dirname, "../../node_modules")
]
});
} catch (e) {
req = require.resolve(depName);
}
catch (e) {
/* allow quiet failure for peer dependencies */
}

// @note: if this is failing, it's likely b/c the dependency isn't built locally
if (!fs.existsSync(req)) return;
if (!existsSync(req)) return;

const content = fs.readFileSync(req, "utf8");
const content = readFileSync(req, "utf8");
if (!content) return;

// Fetch all defined custom properties
postcss.parse(content).walkDecls(/^--/, ({ prop }) => {
parse(content).walkDecls(/^--/, ({ prop }) => {
sharedDefinitions.add(prop);
});
}

// Check dependencies for custom properties
if (!skipDependencies && rootIdx > -1) {
// @todo this is a hard-coded assumption
const pkg = require(path.join(componentRoot, "package.json"));
const pkg = require(join(componentRoot, "package.json"));

if (!pkg) return;

const allDependencies = new Set([
...(Object.keys(pkg.peerDependencies ?? {}) ?? []),
...(Object.keys(pkg.dependencies ?? {}) ?? []),
...(Object.keys(pkg.devDependencies ?? {}) ?? []),
]);

if (allDependencies.size === 0) return;

// @todo this is a hard-coded assumption that we only care about spectrum-css dependencies
for (const dep of [...allDependencies].filter(dep => dep.startsWith("@spectrum-css"))) {
try {
if (!dep.endsWith("vars")) fetchResolutions(dep);
else {
for (const d of ["spectrum-global.css", "components/index.css"]) {
fetchResolutions(`${dep}/dist/${d}`);
}
}
} catch (e) {
/* allow quiet failure for peer dependencies */
fetchResolutions(dep);
}
catch (e) {/* allow quiet failure for peer dependencies */}
}
}

/** @type {Set<string>} */
const localDefinitions = new Set();
root.walkDecls(/^--/, ({ prop }) => {
localDefinitions.add(prop);
});

/* Collect variable use information */
root.walkDecls((decl) => {
// Parse value and get a list of variables used
Expand Down Expand Up @@ -165,7 +164,7 @@ const ruleFunction = (enabled, options = {}) => {
};
};

module.exports.ruleName = ruleName;
module.exports.messages = messages;
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;

module.exports = createPlugin(ruleName, ruleFunction);
export default createPlugin(ruleName, ruleFunction);
Loading

0 comments on commit 7fea737

Please sign in to comment.