diff --git a/.changepacks/changepack_log_eslint_autofix_scope.json b/.changepacks/changepack_log_eslint_autofix_scope.json new file mode 100644 index 000000000..df1a217d2 --- /dev/null +++ b/.changepacks/changepack_log_eslint_autofix_scope.json @@ -0,0 +1,8 @@ +{ + "changes": { + "packages/eslint-plugin/package.json": "Patch", + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "style-order-range only checks styleOrder where the build reads it: a prop of a Devup UI component and a top-level key of a style object passed to css/globalCss/keyframes. A styleOrder key in nested data, in a prop value such as an element passed as a prop, or in a call of a function sharing a name with an Object.prototype member is no longer reported. The array, typography and media rules already skip non-style data, and css-utils-literal-only accepts a css()/keyframes() result held in a const inside a function. No new build errors.", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/bindings/devup-ui-wasm/src/lib.rs b/bindings/devup-ui-wasm/src/lib.rs index 4560410f3..0a2347ac2 100644 --- a/bindings/devup-ui-wasm/src/lib.rs +++ b/bindings/devup-ui-wasm/src/lib.rs @@ -1551,7 +1551,7 @@ mod tests { ); // Test getters - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert_eq!(output.css_file(), Some("devup-ui-0.css".to_string())); assert_eq!(output.map(), Some("//# sourceMappingURL=test".to_string())); assert!(output.css().is_some()); @@ -1951,7 +1951,7 @@ mod tests { assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert!(output.map().is_some()); } @@ -1975,7 +1975,7 @@ mod tests { assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert!(output.map().is_none()); } @@ -2000,7 +2000,7 @@ mod tests { assert!(result.is_err()); if let Err(error) = result { - assert!(!error.is_empty()); + assert_ne!(error, ""); } } diff --git a/libs/css/src/theme_tokens.rs b/libs/css/src/theme_tokens.rs index d8d8fb021..3b2346aed 100644 --- a/libs/css/src/theme_tokens.rs +++ b/libs/css/src/theme_tokens.rs @@ -119,7 +119,7 @@ mod tests { set_typography_keys(vec!["body".to_string(), "title".to_string()]); assert_eq!(get_typography_keys(), vec!["body", "title"]); set_typography_keys(vec![]); - assert!(get_typography_keys().is_empty()); + assert_eq!(get_typography_keys(), Vec::::new()); } #[test] diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 68981f36c..96c6ca430 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -836,8 +836,8 @@ mod tests { alternate: None, }; - assert!(empty.extract().is_empty()); - assert!(empty.into_extract().is_empty()); + assert_eq!(empty.extract(), vec![]); + assert_eq!(empty.into_extract(), vec![]); } #[test] @@ -13598,7 +13598,7 @@ globalCss({ ); assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code.is_empty()); + assert_ne!(output.code, ""); } #[test] @@ -18778,7 +18778,7 @@ export const k = styled('div')({ color: SIZE });", &memory_resolver(CONSTANT_MODULES), ) .unwrap(); - assert!(without_imports.dependencies.is_empty()); + assert_eq!(without_imports.dependencies.len(), 0); let without_constants = extract_with_modules( "/src/Handler.tsx", "import { Box } from '@devup-ui/react';\nimport { handler } from './handler';\nexport const a = ;", diff --git a/libs/extractor/src/tailwind.rs b/libs/extractor/src/tailwind.rs index 87e9f5178..70643aa7c 100644 --- a/libs/extractor/src/tailwind.rs +++ b/libs/extractor/src/tailwind.rs @@ -284,7 +284,7 @@ pub struct TailwindClass { /// non-overlapping) but mutates the existing buffer instead of allocating a new /// `String`. `needle` must be non-empty. fn remove_all_substr(haystack: &mut String, needle: &str) { - debug_assert!(!needle.is_empty()); + debug_assert_ne!(needle, ""); let mut search_from = 0; while let Some(rel) = haystack[search_from..].find(needle) { let at = search_from + rel; @@ -3934,7 +3934,7 @@ mod tests { #[test] fn test_empty_string() { let styles = parse_tailwind_to_styles(""); - assert!(styles.is_empty()); + assert_eq!(styles, vec![]); } #[test] diff --git a/packages/eslint-plugin/src/rules/style-order-range/README.md b/packages/eslint-plugin/src/rules/style-order-range/README.md index 72c2da968..f7808070a 100644 --- a/packages/eslint-plugin/src/rules/style-order-range/README.md +++ b/packages/eslint-plugin/src/rules/style-order-range/README.md @@ -36,6 +36,10 @@ This rule enforces that the `styleOrder` prop must be a number greater than 0 an
``` +## Where it applies + +The rule checks `styleOrder` where the build reads it: a prop of a Devup UI component and a top-level key of a style object passed to `css`, `globalCss` or `keyframes`. A `styleOrder` in nested data (`data-*` props, `props`, an element passed as a prop, nested style objects) is not checked. + ## When Not To Use It If you don't use `styleOrder` props or want to allow any value range, you can disable this rule. diff --git a/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts b/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts index dd3fb3ac6..92e0701a8 100644 --- a/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts +++ b/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts @@ -73,8 +73,33 @@ describe('style-order-range rule', () => { code: '', filename: 'src/app/page.tsx', }, + { + code: 'import { Box, css } from "@devup-ui/react";\n} props={{ styleOrder: 300 }} />;\ncss({ _hover: { styleOrder: 0 }, w: [{ styleOrder: 0 }] })', + filename: 'src/app/page.tsx', + }, + { + code: 'import { css } from "@devup-ui/react";\nconst other = { styleOrder: 0 };\nfoo({ styleOrder: 0 });\ncss({ [`styleOrder`]: 0, "styleOrder": 1 });\ncss({ w: css({ styleOrder: 1 }), styleOrder: 2 })', + filename: 'src/app/page.tsx', + }, + { + code: 'import { css } from "@devup-ui/react";\nconst { styleOrder = 0 } = css({});\ntoString({ styleOrder: 0 });\nnew Foo({ styleOrder: 0 });', + filename: 'src/app/page.tsx', + }, ], invalid: [ + { + code: 'import { css } from "@devup-ui/react";\ncss(base, { styleOrder: 300 })', + filename: 'src/app/page.tsx', + errors: [{ messageId: 'styleOrderRange' }], + }, + { + code: 'import { Box, css } from "@devup-ui/react";\ncss({ w: css({ styleOrder: 0 }), styleOrder: 300 })', + filename: 'src/app/page.tsx', + errors: [ + { messageId: 'styleOrderRange' }, + { messageId: 'styleOrderRange' }, + ], + }, { code: 'import { Box } from "@devup-ui/react";\n', filename: 'src/app/page.tsx', diff --git a/packages/eslint-plugin/src/rules/style-order-range/index.ts b/packages/eslint-plugin/src/rules/style-order-range/index.ts index 035ec7f26..85a9c3cf6 100644 --- a/packages/eslint-plugin/src/rules/style-order-range/index.ts +++ b/packages/eslint-plugin/src/rules/style-order-range/index.ts @@ -92,55 +92,35 @@ export const styleOrderRange = createRule({ }, create(context) { const importStorage = new ImportStorage() - let devupContext: - TSESTree.CallExpression | TSESTree.JSXOpeningElement | null = null return { ImportDeclaration(node) { importStorage.addImportByDeclaration(node) }, - CallExpression(node) { - if ( - importStorage.checkContextType(node) === 'UTIL' && - node.arguments.length === 1 && - node.arguments[0].type === AST_NODE_TYPES.ObjectExpression - ) { - devupContext = node - } - }, - 'CallExpression:exit'(node) { - if (devupContext === node) { - devupContext = null - } - }, Property(node) { + // The build reads `styleOrder` only as a key of a style object handed to the utility itself + const object = node.parent + const call = object.parent if ( - devupContext && node.key.type === AST_NODE_TYPES.Identifier && + !node.computed && node.key.name === 'styleOrder' && node.value.type !== AST_NODE_TYPES.AssignmentPattern && - node.value.type !== AST_NODE_TYPES.TSEmptyBodyFunctionExpression + node.value.type !== AST_NODE_TYPES.TSEmptyBodyFunctionExpression && + object.type === AST_NODE_TYPES.ObjectExpression && + call?.type === AST_NODE_TYPES.CallExpression && + call.arguments.includes(object) && + importStorage.checkContextType(call) === 'UTIL' ) { checkStyleOrderRange(node.value, context) } }, - JSXOpeningElement(node) { - if (importStorage.checkContextType(node) === 'COMPONENT') { - devupContext = node - } - }, - 'JSXOpeningElement:exit'(node) { - if (devupContext === node) { - devupContext = null - } - }, JSXAttribute(node) { - if (!devupContext) return - // styleOrder prop만 체크 if ( node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== 'styleOrder' || - !node.value + !node.value || + importStorage.checkContextType(node.parent) !== 'COMPONENT' ) { return } diff --git a/packages/eslint-plugin/src/utils/import-storage.ts b/packages/eslint-plugin/src/utils/import-storage.ts index 6e0c05d31..5c90a5c32 100644 --- a/packages/eslint-plugin/src/utils/import-storage.ts +++ b/packages/eslint-plugin/src/utils/import-storage.ts @@ -82,7 +82,7 @@ export class ImportStorage { private checkDevupUIUtil(node: TSESTree.CallExpression): boolean { return ( (node.callee.type === AST_NODE_TYPES.Identifier && - node.callee.name in this.imports) || + this.importedName(node.callee.name) !== undefined) || (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && this.importObject.has(node.callee.object.name) && @@ -94,7 +94,7 @@ export class ImportStorage { private checkDevupUIComponent(node: TSESTree.JSXTagNameExpression): boolean { return ( (node.type === AST_NODE_TYPES.JSXIdentifier && - node.name in this.imports) || + this.importedName(node.name) !== undefined) || (node.type === AST_NODE_TYPES.JSXMemberExpression && node.object.type === AST_NODE_TYPES.JSXIdentifier && this.importObject.has(node.object.name) &&