@plumeria/eslint-plugin 19.9.0 → 19.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@ Below are the available rules and the recommended configuration.
8
8
  The `plugin:@plumeria/recommended` config enables the following:
9
9
 
10
10
  - `@plumeria/custom-props-require-import`: **error**
11
+ - `@plumeria/expand-border-shorthands`: **warn**
11
12
  - `@plumeria/no-combinator`: **error**
12
13
  - `@plumeria/no-destructure`: **error**
13
14
  - `@plumeria/no-inline-object`: **error**
@@ -82,6 +83,27 @@ the import.
82
83
 
83
84
  Accepts `{ styleProp }`; see [Configuring the styling prop](#configuring-the-styling-prop).
84
85
 
86
+ ### expand-border-shorthands
87
+
88
+ Expands a border shorthand that bundles a width, a style and a color —
89
+ `border`, `borderBlock`, `borderInline`, and the eight edge forms — into the
90
+ three declarations it stands for. Those bundles are the only properties left
91
+ that cross an axis shorthand without either containing the other, so expanding
92
+ them turns the last unrankable pairs into ordinary shorthand-to-longhand ones.
93
+
94
+ Fixable. A value it cannot split, such as `var(--edge)` or `inherit`, is
95
+ reported without a fix: leaving it silent would let the expanded declarations
96
+ elsewhere outrank it.
97
+
98
+ ```js
99
+ borderTop: '1px solid red'
100
+ // becomes
101
+ borderTopWidth: '1px', borderTopStyle: 'solid', borderTopColor: 'red'
102
+ ```
103
+
104
+ A shorthand resets what it omits, so `borderBlock: 'solid'` expands with
105
+ `medium` and `currentcolor` written out.
106
+
85
107
  ### no-combinator
86
108
 
87
109
  Disallow combinators `>`, `+`, `~` and descendant combinator (space) unless inside functional pseudo-classes.
@@ -139,6 +161,8 @@ Accepts `{ styleProp }`; see [Configuring the styling prop](#configuring-the-sty
139
161
 
140
162
  Disallow unknown CSS properties in camelCase within `css.create`, `css.keyframes`, and `css.viewTransition`.
141
163
 
164
+ In `css.create`, a key that holds an object is reported with a hint that nested selectors start with `:`, `[` or `@`.
165
+
142
166
  ### no-unresolved-composition
143
167
 
144
168
  A safety net for `css.use()`. Warns when its result is combined with other class names or passed to another function, or when a function key is passed to it. Pass every style to one `css.use()` call, and apply function keys to the styling prop.
@@ -174,8 +198,8 @@ Validates at-rules inside `css.create()`. It accepts `@media`, `@container`, `@s
174
198
 
175
199
  ## Optional rules
176
200
 
177
- These rules are not enabled by `plumeria.configs.recommended`. Enable only the
178
- policy or transformation that fits the project:
201
+ These rules are not enabled by `plumeria.configs.recommended`. Enable the
202
+ spelling policy that fits the project:
179
203
 
180
204
  ```js
181
205
  export default [
@@ -183,33 +207,11 @@ export default [
183
207
  {
184
208
  rules: {
185
209
  '@plumeria/no-logical-properties': 'warn',
186
- '@plumeria/expand-border-shorthands': 'warn',
187
210
  },
188
211
  },
189
212
  ];
190
213
  ```
191
214
 
192
- ### expand-border-shorthands
193
-
194
- Expands a border shorthand that bundles a width, a style and a color —
195
- `border`, `borderBlock`, `borderInline`, and the eight edge forms — into the
196
- three declarations it stands for. Those bundles are the only properties left
197
- that cross an axis shorthand without either containing the other, so expanding
198
- them turns the last unrankable pairs into ordinary shorthand-to-longhand ones.
199
-
200
- Fixable. A value it cannot split, such as `var(--edge)` or `inherit`, is
201
- reported without a fix: leaving it silent would let the expanded declarations
202
- elsewhere outrank it.
203
-
204
- ```js
205
- borderTop: '1px solid red'
206
- // becomes
207
- borderTopWidth: '1px', borderTopStyle: 'solid', borderTopColor: 'red'
208
- ```
209
-
210
- A shorthand resets what it omits, so `borderBlock: 'solid'` expands with
211
- `medium` and `currentcolor` written out.
212
-
213
215
  ### no-physical-properties / no-logical-properties
214
216
 
215
217
  Disallow one of the two names a property can carry, so a project writes edges
@@ -226,19 +228,24 @@ appears under one spelling only can never meet its other spelling on an element.
226
228
  A shorthand with no single counterpart, such as `borderBlockWidth`, is outside
227
229
  either rule and stays reported.
228
230
 
231
+ The bundler plugins turn the matching rule on in the build lint as well:
232
+ `withoutPhysicalProperties` runs `no-physical-properties`, and
233
+ `withoutLogicalProperties` runs `no-logical-properties`, `{ sizes }` included.
234
+
229
235
  ## CLI (plumerialint)
230
236
 
231
237
  This package provides a CLI command, `plumerialint`, as a convenient way
232
238
  to run Plumeria's custom ESLint rules.
233
239
 
234
- It uses `oxlint` internally for fast linting with code snippets in output.
240
+ It runs `oxlint`, which this package depends on, for fast linting with code
241
+ snippets in output, so oxlint needs no separate install.
235
242
 
236
243
  ### Installation
237
244
 
238
245
  ```bash
239
- npm install -D @plumeria/eslint-plugin oxlint
246
+ npm install -D @plumeria/eslint-plugin
240
247
  # or
241
- pnpm add -D @plumeria/eslint-plugin oxlint
248
+ pnpm add -D @plumeria/eslint-plugin
242
249
  ```
243
250
 
244
251
  ### Usage
@@ -250,16 +257,6 @@ plumerialint
250
257
  The process exits with a non-zero status code if any errors or warnings are found,
251
258
  making it suitable for use in CI and build pipelines.
252
259
 
253
- Example usage in `package.json`:
254
-
255
- ```json
256
- {
257
- "scripts": {
258
- "lint": "plumerialint"
259
- }
260
- }
261
- ```
262
-
263
260
  ### Aborting Builds on Lint Errors (Parallel Pipeline)
264
261
 
265
262
  You can run `plumerialint` in parallel with your build command (e.g. `next build` or `vite build`) using the `--` separator:
@@ -274,4 +271,4 @@ You can run `plumerialint` in parallel with your build command (e.g. `next build
274
271
 
275
272
  If `plumerialint` detects any styling errors or warnings, it will print the diagnostics, kill the build process immediately, and exit with a non-zero code. This avoids compiling when styling validation fails.
276
273
 
277
- **Note:** `oxlint` is required as `plumerialint` uses it internally.
274
+ With `@plumeria/next-plugin` and `@plumeria/unplugin` (except on esbuild and Bun), this lint is integrated into the build by default, so it needs no setup. Pass `lint: false` to the plugin to build without it.
package/bin/oxlint.js CHANGED
@@ -4,6 +4,11 @@ const { spawn } = require('child_process');
4
4
  const process = require('process');
5
5
  const path = require('path');
6
6
  const oxlintConfig = path.join(__dirname, '..', 'oxlint.json');
7
+ const oxlintBin = path.join(
8
+ path.dirname(require.resolve('oxlint/package.json')),
9
+ 'bin',
10
+ 'oxlint',
11
+ );
7
12
 
8
13
  const doubleDashIndex = process.argv.indexOf('--');
9
14
  let oxlintExtraArgs = [];
@@ -20,22 +25,22 @@ if (doubleDashIndex !== -1) {
20
25
  oxlintExtraArgs = process.argv.slice(2);
21
26
  }
22
27
 
23
- const oxlintArgs = ['-c', oxlintConfig, '--deny-warnings', ...oxlintExtraArgs];
28
+ const oxlintArgs = [
29
+ oxlintBin,
30
+ '-c',
31
+ oxlintConfig,
32
+ '--deny-warnings',
33
+ '--no-error-on-unmatched-pattern',
34
+ ...oxlintExtraArgs,
35
+ ];
24
36
 
25
37
  function handleOxlintError(err) {
26
- if (err.code === 'ENOENT') {
27
- console.error('\n✖ oxlint is not installed.');
28
- console.error('➡︎ plumerialint uses oxlint.');
29
- console.error('✔ please install oxlint.\n');
30
- process.exit(1);
31
- } else {
32
- console.error('Error running oxlint:', err.message);
33
- process.exit(1);
34
- }
38
+ console.error('Error running oxlint:', err.message);
39
+ process.exit(1);
35
40
  }
36
41
 
37
42
  if (!buildCommand) {
38
- const child = spawn('oxlint', oxlintArgs, { stdio: 'inherit' });
43
+ const child = spawn(process.execPath, oxlintArgs, { stdio: 'inherit' });
39
44
  child.on('error', handleOxlintError);
40
45
  child.on('close', (code) => {
41
46
  process.exit(code || 0);
@@ -47,7 +52,7 @@ if (!buildCommand) {
47
52
  let buildCode = null;
48
53
  let aborted = false;
49
54
 
50
- const oxlintChild = spawn('oxlint', oxlintArgs, { stdio: 'inherit' });
55
+ const oxlintChild = spawn(process.execPath, oxlintArgs, { stdio: 'inherit' });
51
56
 
52
57
  const fullBuildCommand =
53
58
  buildArgs.length > 0
@@ -57,6 +62,7 @@ if (!buildCommand) {
57
62
  const buildChild = spawn(fullBuildCommand, {
58
63
  stdio: 'inherit',
59
64
  shell: true,
65
+ env: { ...process.env, PLUMERIA_LINT_GUARD: '1' },
60
66
  });
61
67
 
62
68
  function abort(exitCode, failedSource) {
@@ -0,0 +1,11 @@
1
+ export declare const GUARD_ENV = "PLUMERIA_LINT_GUARD";
2
+ type Spelling = boolean | {
3
+ sizes?: boolean;
4
+ } | undefined;
5
+ export interface SpellingOptions {
6
+ withoutLogicalProperties?: Spelling;
7
+ withoutPhysicalProperties?: Spelling;
8
+ }
9
+ export declare function spellingRules(options: SpellingOptions): Record<string, unknown>;
10
+ export declare function startLintGuard(rules?: Record<string, unknown>): boolean;
11
+ export {};
package/dist/guard.js ADDED
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.GUARD_ENV = void 0;
37
+ exports.spellingRules = spellingRules;
38
+ exports.startLintGuard = startLintGuard;
39
+ const child_process_1 = require("child_process");
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ exports.GUARD_ENV = 'PLUMERIA_LINT_GUARD';
44
+ const BASE_CONFIG = path.join(__dirname, '..', 'oxlint.json');
45
+ function spellingRule(spelling) {
46
+ if (!spelling)
47
+ return undefined;
48
+ return typeof spelling === 'object' && spelling.sizes
49
+ ? ['error', { sizes: true }]
50
+ : 'error';
51
+ }
52
+ function spellingRules(options) {
53
+ const rules = {};
54
+ const logical = spellingRule(options.withoutLogicalProperties);
55
+ const physical = spellingRule(options.withoutPhysicalProperties);
56
+ if (logical)
57
+ rules['@plumeria/no-logical-properties'] = logical;
58
+ if (physical)
59
+ rules['@plumeria/no-physical-properties'] = physical;
60
+ return rules;
61
+ }
62
+ function lintConfig(rules) {
63
+ if (Object.keys(rules).length === 0)
64
+ return BASE_CONFIG;
65
+ const file = path.join(os.tmpdir(), `plumeria-oxlint-${process.pid}.json`);
66
+ fs.writeFileSync(file, JSON.stringify({ extends: [BASE_CONFIG], rules }));
67
+ return file;
68
+ }
69
+ function startLintGuard(rules = {}) {
70
+ if (process.env[exports.GUARD_ENV])
71
+ return false;
72
+ process.env[exports.GUARD_ENV] = '1';
73
+ const config = lintConfig(rules);
74
+ const child = (0, child_process_1.spawn)(process.execPath, [
75
+ path.join(path.dirname(require.resolve('oxlint/package.json')), 'bin', 'oxlint'),
76
+ '-c',
77
+ config,
78
+ '--deny-warnings',
79
+ '--no-error-on-unmatched-pattern',
80
+ ], { stdio: 'inherit' });
81
+ const exit = process.exit.bind(process);
82
+ let lintCode = null;
83
+ let pendingCode = null;
84
+ child.on('error', (error) => {
85
+ console.error(`\n✖ [plumeria] Could not run oxlint: ${error.message}`);
86
+ exit(1);
87
+ });
88
+ child.on('close', (code) => {
89
+ if (config !== BASE_CONFIG)
90
+ fs.rmSync(config, { force: true });
91
+ lintCode = code ?? 1;
92
+ if (lintCode !== 0) {
93
+ console.error('\n✖ [plumeria] Linting failed. Aborting build...');
94
+ return exit(lintCode);
95
+ }
96
+ if (pendingCode !== null)
97
+ exit(pendingCode);
98
+ });
99
+ process.exit = ((code) => {
100
+ if (lintCode !== null)
101
+ return exit(code);
102
+ pendingCode = code;
103
+ });
104
+ return true;
105
+ }
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ const configs = {
50
50
  },
51
51
  rules: {
52
52
  '@plumeria/custom-props-require-import': 'error',
53
+ '@plumeria/expand-border-shorthands': 'warn',
53
54
  '@plumeria/no-combinator': 'error',
54
55
  '@plumeria/no-destructure': 'error',
55
56
  '@plumeria/no-inline-object': 'error',
@@ -13,6 +13,7 @@ exports.noUnknownCssProperties = {
13
13
  },
14
14
  messages: {
15
15
  unknownProperty: "Unknown CSS property '{{ name }}'.",
16
+ unknownNestedProperty: "Unknown CSS property '{{ name }}'. Nested selectors start with ':', '[' or '@'.",
16
17
  },
17
18
  schema: [],
18
19
  },
@@ -38,6 +39,7 @@ exports.noUnknownCssProperties = {
38
39
  },
39
40
  CallExpression(node) {
40
41
  let isCssProperties = false;
42
+ let isCreate = false;
41
43
  if (node.callee.type === 'MemberExpression') {
42
44
  if (node.callee.object.type === 'Identifier' &&
43
45
  plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
@@ -48,6 +50,7 @@ exports.noUnknownCssProperties = {
48
50
  propertyName === 'keyframes' ||
49
51
  propertyName === 'viewTransition') {
50
52
  isCssProperties = true;
53
+ isCreate = propertyName === 'create';
51
54
  }
52
55
  }
53
56
  }
@@ -57,6 +60,7 @@ exports.noUnknownCssProperties = {
57
60
  alias === 'keyframes' ||
58
61
  alias === 'viewTransition') {
59
62
  isCssProperties = true;
63
+ isCreate = alias === 'create';
60
64
  }
61
65
  }
62
66
  if (isCssProperties) {
@@ -67,18 +71,18 @@ exports.noUnknownCssProperties = {
67
71
  return;
68
72
  const style = (0, styleObject_1.styleObjectFromValue)(prop.value);
69
73
  if (style)
70
- checkStyleObject(style);
74
+ checkStyleObject(style, isCreate);
71
75
  });
72
76
  }
73
77
  });
74
78
  }
75
79
  },
76
80
  };
77
- function checkStyleObject(node) {
81
+ function checkStyleObject(node, isCreate) {
78
82
  node.properties.forEach((prop) => {
79
83
  if (prop.type === 'Property') {
80
84
  if (prop.value.type === 'ObjectExpression') {
81
- checkStyleObject(prop.value);
85
+ checkStyleObject(prop.value, isCreate);
82
86
  }
83
87
  let isCheckable = false;
84
88
  let keyName = '';
@@ -103,7 +107,9 @@ exports.noUnknownCssProperties = {
103
107
  if (!knownProperties.has(kebabName)) {
104
108
  context.report({
105
109
  node: prop.key,
106
- messageId: 'unknownProperty',
110
+ messageId: isCreate && prop.value.type === 'ObjectExpression'
111
+ ? 'unknownNestedProperty'
112
+ : 'unknownProperty',
107
113
  data: {
108
114
  name: keyName,
109
115
  },
package/oxlint.json CHANGED
@@ -6,10 +6,12 @@
6
6
  }
7
7
  ],
8
8
  "plugins": [],
9
+ "categories": { "correctness": "off" },
9
10
  "ignorePatterns": ["**/*.svelte", "**/*.vue"],
10
11
 
11
12
  "rules": {
12
13
  "@plumeria/custom-props-require-import": "error",
14
+ "@plumeria/expand-border-shorthands": "warn",
13
15
  "@plumeria/no-combinator": "error",
14
16
  "@plumeria/no-destructure": "error",
15
17
  "@plumeria/no-inline-object": "error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/eslint-plugin",
3
- "version": "19.9.0",
3
+ "version": "19.10.0",
4
4
  "description": "Plumeria ESLint plugin",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",
@@ -29,6 +29,10 @@
29
29
  "types": "./dist/index.d.ts",
30
30
  "default": "./dist/index.js"
31
31
  },
32
+ "./guard": {
33
+ "types": "./dist/guard.d.ts",
34
+ "default": "./dist/guard.js"
35
+ },
32
36
  "./oxlint-plugin": "./oxlint-plugin.js"
33
37
  },
34
38
  "files": [
@@ -53,8 +57,9 @@
53
57
  },
54
58
  "dependencies": {
55
59
  "known-css-properties": "^0.37.0",
60
+ "oxlint": "1.87.0",
56
61
  "zss-engine": "2.9.1",
57
- "@plumeria/compiler": "19.9.0"
62
+ "@plumeria/compiler": "19.10.0"
58
63
  },
59
64
  "scripts": {
60
65
  "build": "rimraf dist && pnpm cjs",