Repository: microsoft/griffel
Pull request #446 (2023-09-29 08:38:07)

chore: remove unstable-css-lint rule
This PR removes `unstable-css-lint` rule introduced in #434 as we rely on `stylelint` and custom syntax in future.

github-actions[bot]: ## 📊 Bundle size report

<sub>🤖 This report was generated against <a href='https://github.com/microsoft/griffel/commit/480aba5a6d8a3b295c10dffe80c2ff4e228f9b3d'>480aba5a6d8a3b295c10dffe80c2ff4e228f9b3d</a></sub>

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/change/@griffel-eslint-plugin-ac0e0e3e-04c3-400f-a68e-9912b841e06b.json b/change/@griffel-eslint-plugin-ac0e0e3e-04c3-400f-a68e-9912b841e06b.json
new file mode 100644
index 0000000..70b0edb
--- /dev/null
+++ b/change/@griffel-eslint-plugin-ac0e0e3e-04c3-400f-a68e-9912b841e06b.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "chore: remove unstable-css-lint rule",
+  "packageName": "@griffel/eslint-plugin",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json
index 0d1a418..77a20c1 100644
--- a/packages/eslint-plugin/package.json
+++ b/packages/eslint-plugin/package.json
@@ -8,12 +8,8 @@
     "url": "https://github.com/microsoft/griffel"
   },
   "dependencies": {
-    "@griffel/babel-preset": "^1.5.1",
-    "@babel/core": "^7.12.13",
     "@typescript-eslint/utils": "^5.47.0",
     "csstype": "^3.1.2",
-    "synckit": "^0.8.5",
-    "stylelint": "^15.10.3",
     "tslib": "^2.1.0"
   },
   "peerDependencies": {
diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts
index 45cf904..6fb7dbd 100644
--- a/packages/eslint-plugin/src/index.ts
+++ b/packages/eslint-plugin/src/index.ts
@@ -3,7 +3,6 @@ import { hookNamingRule } from './rules/hook-naming';
 import { noShorthandsRule } from './rules/no-shorthands';
 import { pseudoElementNamingRule } from './rules/pseudo-element-naming';
 import { stylesFileRule } from './rules/styles-file';
-import { cssLintRule } from './rules/css-lint';
 
 export = {
   configs: {
@@ -14,6 +13,5 @@ export = {
     'no-shorthands': noShorthandsRule,
     'styles-file': stylesFileRule,
     'pseudo-element-naming': pseudoElementNamingRule,
-    'unstable-css-lint': cssLintRule,
   },
 };
diff --git a/packages/eslint-plugin/src/rules/css-lint/css-lint.md b/packages/eslint-plugin/src/rules/css-lint/css-lint.md
deleted file mode 100644
index 6d3f665..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/css-lint.md
+++ /dev/null
@@ -1,1 +0,0 @@
-WIP
\ No newline at end of file
diff --git a/packages/eslint-plugin/src/rules/css-lint/css-lint.ts b/packages/eslint-plugin/src/rules/css-lint/css-lint.ts
deleted file mode 100644
index 71e18a0..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/css-lint.ts
+++ /dev/null
@@ -1,188 +0,0 @@
-import { ASTUtils, TSESTree } from '@typescript-eslint/utils';
-import { BabelPluginOptions } from '@griffel/babel-preset';
-
-import { transformSync } from './transform-sync';
-import {
-  isIdentifier,
-  isMakeStylesIdentifier,
-  isObjectExpression,
-  isProperty,
-  isMakeResetStylesIdentifier,
-} from '../../utils/helpers';
-import { createRule } from '../../utils/createRule';
-import { stylelintSync } from './stylelint-sync';
-
-type Options = [BabelPluginOptions & { stylelintConfigFile?: string }];
-
-type MessageIds = 'stylelint';
-
-const lintCSS: (css: string, stylelintConfigFile?: string) => string[] = (css, stylelintConfigFile) => {
-  const uniqueErrors: Record<string /** ruleId */, string> = {};
-  const { error, stylelintErrors } = stylelintSync(css, stylelintConfigFile);
-
-  if (error) {
-    throw new Error(`${error} - this is not expected: please report a bug if it happens`);
-  }
-
-  // Since we can only map errors to the entire style slot, we should report unique errors
-  for (const error of stylelintErrors) {
-    uniqueErrors[error.rule] = error.text;
-  }
-
-  return Object.values(uniqueErrors);
-};
-
-// TODO createRule does not generate docs url properly for sub-directory
-export const cssLintRule = createRule<Options, MessageIds>({
-  name: 'css-lint',
-  meta: {
-    type: 'problem',
-    schema: [
-      {
-        type: 'object',
-        properties: {
-          stylelintConfigFile: {
-            type: 'string',
-            description:
-              'Path to a .stylelintrc config file. By default, stylelint will try to find a config file in a parent directory',
-          },
-          generateMetadata: {
-            type: 'boolean',
-          },
-          modules: {
-            description: 'Configures the import name and source of Griffel',
-            type: 'array',
-            items: {
-              type: 'object',
-              required: ['moduleSource', 'importName'],
-              properties: {
-                moduleSource: {
-                  type: 'string',
-                },
-                importName: {
-                  type: 'string',
-                },
-              },
-            },
-          },
-          babelOptions: {
-            description:
-              'Configures the Griffel babel preset, see https://github.com/microsoft/griffel/blob/main/packages/babel-preset/README.md',
-            type: 'object',
-            properties: {
-              plugins: {
-                type: 'array',
-              },
-              presets: {
-                type: 'array',
-              },
-            },
-          },
-          evaluationRules: {
-            description:
-              'Configures the Griffel babel preset, see https://github.com/microsoft/griffel/blob/main/packages/babel-preset/README.md',
-            type: 'array',
-            items: {
-              type: 'object',
-              required: ['action'],
-              properties: {},
-            },
-          },
-          projectRoot: {
-            type: 'string',
-          },
-        },
-      },
-    ],
-    messages: {
-      stylelint: 'stylelint: {{stylelint}}',
-    },
-    docs: {
-      description: 'WIP - not ready yet',
-      recommended: 'error',
-    },
-  },
-  defaultOptions: [{}],
-  create(context) {
-    // Map of all style slots to the makeStyles return function name
-    const slotPropertiesByDeclaratorId = new Map<string, Map<string, TSESTree.Property>>();
-    // map of all styles to the return of the makeResetStyles function name
-    const resetEntriesByDeclaratorId = new Map<string, TSESTree.CallExpression>();
-    const { stylelintConfigFile, ...pluginOptions } = context.options[0];
-
-    return {
-      'Program:exit'(node) {
-        if (!context.getPhysicalFilename) {
-          throw new Error(
-            'No physical filename could be found - this is not expected: please report this to Griffel authors',
-          );
-        }
-
-        const filename = context.getPhysicalFilename();
-        const source = context.getSourceCode().getText();
-        const { metadata } = transformSync(source, {
-          filename,
-          pluginOptions: {
-            ...pluginOptions,
-            // This rule cannot do anything without metadata - always generate them
-            generateMetadata: true,
-          },
-        });
-
-        const { cssEntries, cssResetEntries } = metadata;
-
-        for (const [declaratorId, cssRulesBySlot] of Object.entries(cssEntries)) {
-          for (const [slotName, rules] of Object.entries(cssRulesBySlot)) {
-            const errors = lintCSS(rules.join(''), stylelintConfigFile);
-
-            const property = slotPropertiesByDeclaratorId.get(declaratorId)?.get(slotName);
-            for (const error of errors) {
-              context.report({ messageId: 'stylelint', node: property ?? node, data: { stylelint: error } });
-            }
-          }
-        }
-
-        for (const [resetDeclarator, [resetCSS]] of Object.entries(cssResetEntries)) {
-          const errors = lintCSS(resetCSS, stylelintConfigFile);
-          const property = resetEntriesByDeclaratorId.get(resetDeclarator);
-          for (const error of Object.values(errors)) {
-            context.report({ messageId: 'stylelint', node: property ?? node, data: { stylelint: error } });
-          }
-        }
-      },
-      CallExpression(node) {
-        const variableDeclarator = node.parent;
-        if (!ASTUtils.isVariableDeclarator(variableDeclarator)) {
-          return;
-        }
-
-        let declaratorId = '';
-        if (isIdentifier(variableDeclarator.id)) {
-          declaratorId = variableDeclarator.id.name;
-        }
-
-        if (isMakeResetStylesIdentifier(node.callee)) {
-          resetEntriesByDeclaratorId.set(declaratorId, node);
-        }
-
-        if (isMakeStylesIdentifier(node.callee)) {
-          const slotProperties = new Map<string, TSESTree.Property>();
-          const slots = node.arguments[0];
-          if (isObjectExpression(slots)) {
-            slots.properties.forEach(slot => {
-              if (
-                isProperty(slot) &&
-                isIdentifier(slot.key) &&
-                (isObjectExpression(slot.value) || isIdentifier(slot.value))
-              ) {
-                slotProperties.set(slot.key.name, slot);
-              }
-            });
-          }
-
-          slotPropertiesByDeclaratorId.set(declaratorId, slotProperties);
-        }
-      },
-    };
-  },
-});
diff --git a/packages/eslint-plugin/src/rules/css-lint/index.ts b/packages/eslint-plugin/src/rules/css-lint/index.ts
deleted file mode 100644
index a364efe..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/index.ts
+++ /dev/null
@@ -1,1 +0,0 @@
-export { cssLintRule } from './css-lint';
diff --git a/packages/eslint-plugin/src/rules/css-lint/stylelint-sync.ts b/packages/eslint-plugin/src/rules/css-lint/stylelint-sync.ts
deleted file mode 100644
index 3e380f1..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/stylelint-sync.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { createSyncFn } from 'synckit';
-import { StylelintSyncReturn } from './types';
-
-export const stylelintSync: (css: string, stylelintConfigFile?: string) => StylelintSyncReturn = createSyncFn(
-  require.resolve('./stylelint-task'),
-  3000,
-);
diff --git a/packages/eslint-plugin/src/rules/css-lint/stylelint-task.ts b/packages/eslint-plugin/src/rules/css-lint/stylelint-task.ts
deleted file mode 100644
index 30f2597..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/stylelint-task.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { runAsWorker } from 'synckit';
-import * as stylelint from 'stylelint';
-import { StylelintSyncReturn } from './types';
-
-runAsWorker(async (css: string, configFile: string): Promise<StylelintSyncReturn> => {
-  try {
-    const { results } = await stylelint.lint({ code: css, configFile });
-    const warnings = results.map(r => r.warnings);
-    return { stylelintErrors: warnings.flat() };
-  } catch (e) {
-    let error = 'Error running stylelint, please report to Griffel maintainers';
-    if (e instanceof Error) {
-      error = `${error}: ${e.message}`;
-    }
-
-    return {
-      error,
-      stylelintErrors: [],
-    };
-  }
-});
diff --git a/packages/eslint-plugin/src/rules/css-lint/transform-sync.ts b/packages/eslint-plugin/src/rules/css-lint/transform-sync.ts
deleted file mode 100644
index 05e527c..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/transform-sync.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import * as Babel from '@babel/core';
-import griffelPreset, { BabelPluginMetadata, BabelPluginOptions } from '@griffel/babel-preset';
-
-export type TransformOptions = {
-  filename: string;
-  pluginOptions: BabelPluginOptions;
-};
-
-export type TransformResult = {
-  code: string;
-  metadata: BabelPluginMetadata;
-};
-
-/**
- * Transforms passed source code with Babel, uses user's config for parsing, but ignores it for transforms.
- */
-export function transformSync(sourceCode: string, options: TransformOptions): TransformResult {
-  // Parse the code first so Babel will use user's babel config for parsing
-  // During transforms we don't want to use user's config
-  const babelAST = Babel.parseSync(sourceCode, {
-    caller: { name: 'griffel' },
-    filename: options.filename,
-    // TODO - we might need to add some babel config in the eslint rule options
-    // if the user doesn't want to use their normal babel config for this
-    // but let's only add it if it becomes necessary
-  });
-
-  if (babelAST === null) {
-    throw new Error(`Failed to create AST for "${options.filename}" due unknown Babel error...`);
-  }
-
-  const babelFileResult = Babel.transformFromAstSync(babelAST, sourceCode, {
-    // Ignore all user's configs and apply only our plugin
-    babelrc: false,
-    configFile: false,
-    presets: [[griffelPreset, options.pluginOptions]],
-
-    filename: options.filename,
-    sourceFileName: options.filename,
-  });
-
-  if (babelFileResult === null) {
-    throw new Error(`Failed to transform "${options.filename}" due unknown Babel error...`);
-  }
-
-  return {
-    metadata: babelFileResult.metadata as unknown as BabelPluginMetadata,
-    code: babelFileResult.code as string,
-  };
-}
diff --git a/packages/eslint-plugin/src/rules/css-lint/types.ts b/packages/eslint-plugin/src/rules/css-lint/types.ts
deleted file mode 100644
index affa2de..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Warning } from 'stylelint';
-
-export interface StylelintSyncReturn {
-  error?: string;
-  stylelintErrors: Warning[];
-}


Test patch:
diff --git a/packages/eslint-plugin/src/rules/css-lint/.stylelintrc-test.js b/packages/eslint-plugin/src/rules/css-lint/.stylelintrc-test.js
deleted file mode 100644
index cc5f32e..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/.stylelintrc-test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * A test stylelint config for tests
- */
-
-module.exports = {
-  rules: {
-    'selector-anb-no-unmatchable': [true],
-  },
-};
diff --git a/packages/eslint-plugin/src/rules/css-lint/css-lint.test.ts b/packages/eslint-plugin/src/rules/css-lint/css-lint.test.ts
deleted file mode 100644
index 4253712..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/css-lint.test.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import * as path from 'path';
-import { TSESLint } from '@typescript-eslint/utils';
-import { cssLintRule } from './css-lint';
-
-const stylelintConfigFile = require.resolve('./.stylelintrc-test.js');
-const ruleTesterInstance = new TSESLint.RuleTester({
-  parser: path.resolve('./node_modules/@typescript-eslint/parser'),
-  parserOptions: {
-    ecmaVersion: 2018,
-    sourceType: 'module',
-  },
-});
-ruleTesterInstance.run('css-lint', cssLintRule, {
-  valid: [
-    {
-      options: [{ stylelintConfigFile }],
-      name: 'should pass stylelint',
-      code: `
-      import { makeStyles } from '@griffel/react'
-      const useStyles = makeStyles({
-        root: {
-          ':nth-child(1)': {
-            color: 'red',
-          }
-        }
-      })
-      `,
-      filename: './useComponentStyles.styles.ts',
-    },
-  ],
-  invalid: [
-    {
-      options: [{ stylelintConfigFile }],
-      name: 'should fail stylelint (reset)',
-      errors: [
-        {
-          messageId: 'stylelint',
-          data: { stylelint: 'Unexpected unmatchable An+B selector ":nth-child" (selector-anb-no-unmatchable)' },
-        },
-      ],
-      code: `
-      import { makeResetStyles } from '@griffel/react'
-      const useResetStyles = makeResetStyles({
-        ':nth-child(0)': {
-          color: 'red',
-          backgroundColor: 'green',
-        }
-      })
-      `,
-      filename: './useComponentStyles.styles.ts',
-    },
-    {
-      options: [{ stylelintConfigFile }],
-      name: 'should fail stylelint',
-      errors: [
-        {
-          messageId: 'stylelint',
-          data: { stylelint: 'Unexpected unmatchable An+B selector ":nth-child" (selector-anb-no-unmatchable)' },
-        },
-      ],
-      code: `
-      import { makeStyles } from '@griffel/react'
-      const useStyles = makeStyles({
-        root: {
-          ':nth-child(0)': {
-            color: 'red',
-          }
-        }
-      })
-      `,
-      filename: './useComponentStyles.styles.ts',
-    },
-    {
-      options: [{ stylelintConfigFile: stylelintConfigFile }],
-      name: 'should only warn once per error',
-      errors: [
-        {
-          messageId: 'stylelint',
-          data: { stylelint: 'Unexpected unmatchable An+B selector ":nth-child" (selector-anb-no-unmatchable)' },
-        },
-      ],
-      code: `
-      import { makeStyles } from '@griffel/react'
-      const useStyles = makeStyles({
-        root: {
-          ':nth-child(0)': {
-            color: 'red',
-            backgroundColor: 'green',
-          }
-        }
-      })
-      `,
-      filename: './useComponentStyles.styles.ts',
-    },
-  ],
-});
diff --git a/packages/eslint-plugin/src/rules/css-lint/stylelint-sync.test.ts b/packages/eslint-plugin/src/rules/css-lint/stylelint-sync.test.ts
deleted file mode 100644
index 9ec807a..0000000
--- a/packages/eslint-plugin/src/rules/css-lint/stylelint-sync.test.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { stylelintSync } from './stylelint-sync';
-
-const stylelintcConfigFile = require.resolve('./.stylelintrc-test.js');
-
-describe('stylelintSync', () => {
-  it('should by sync', () => {
-    const { stylelintErrors } = stylelintSync('.foo{color: red;}', stylelintcConfigFile);
-    expect(stylelintErrors).toEqual([]);
-  });
-
-  it('should report crash errors', () => {
-    const { error } = stylelintSync('.foo{color: red;}');
-    expect(error).toContain(
-      `Error running stylelint, please report to Griffel maintainers: No configuration provided for`,
-    );
-  });
-
-  it('should return stylelint errors', () => {
-    const { stylelintErrors } = stylelintSync('.foo:nth-child(0){color: red;}', stylelintcConfigFile);
-    expect(stylelintErrors).toMatchInlineSnapshot(`
-      Array [
-        Object {
-          "column": 6,
-          "endColumn": 19,
-          "endLine": 1,
-          "line": 1,
-          "rule": "selector-anb-no-unmatchable",
-          "severity": "error",
-          "text": "Unexpected unmatchable An+B selector \\":nth-child\\" (selector-anb-no-unmatchable)",
-        },
-      ]
-    `);
-  });
-});