Skip to content

Commit

Permalink
Fix duplicate css import in eslint plugin (#1509)
Browse files Browse the repository at this point in the history
  • Loading branch information
dddlr committed Sep 6, 2023
1 parent 258d6d1 commit 59687ab
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/nice-pots-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/eslint-plugin': patch
---

Fix @compiled/eslint-plugin no-css-prop-without-css-function rule adding duplicate css import
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,54 @@ tester.run(
<div css={css({ backgroundColor: 'red' })} />;
`,
},
{
// Inline object expression without function
// with existing css import
errors: [
{
messageId: 'noCssFunction',
},
],
code: outdent`
import React from 'react';
import { css } from '@compiled/react';
<div css={{ backgroundColor: 'red' }} />;
`,
output: outdent`
import React from 'react';
import { css } from '@compiled/react';
<div css={css({ backgroundColor: 'red' })} />;
`,
},
{
// Inline object expression without function
// with existing css import, imported as something else
errors: [
{
messageId: 'noCssFunction',
},
],
code: outdent`
import React from 'react';
import { css as css2 } from '@compiled/react';
import { css } from 'example';
css();
<div css={{ backgroundColor: 'red' }} />;
`,
output: outdent`
import React from 'react';
import { css as css2 } from '@compiled/react';
import { css } from 'example';
css();
<div css={css2({ backgroundColor: 'red' })} />;
`,
},
{
errors: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
isDOMElement,
traverseUpToJSXOpeningElement,
} from '../../utils/ast';
import { addImportToDeclaration, buildImportDeclaration } from '../../utils/ast-to-string';
import {
addImportToDeclaration,
buildImportDeclaration,
getImportedName,
} from '../../utils/ast-to-string';

type Q<T> = T extends TSESLint.Scope.Definition
? T['type'] extends 'Variable'
Expand Down Expand Up @@ -90,12 +94,17 @@ const fixWrapper = (node: CSSValue, context: Context) => {
const compiledImports = findTSCompiledImportDeclarations(context);
const source = context.getSourceCode();

// The string that `css` from `@compiled/css` is imported as
const cssImportName = getImportedName(compiledImports, 'css');

if (compiledImports.length > 0) {
// Import found, add the specifier to it
const [firstCompiledImport] = compiledImports;
const specifiersString = addImportToDeclaration(firstCompiledImport, ['css']);
if (!cssImportName) {
// Import found, add the specifier to it
const [firstCompiledImport] = compiledImports;
const specifiersString = addImportToDeclaration(firstCompiledImport, ['css']);

yield fixer.replaceText(firstCompiledImport, specifiersString);
yield fixer.replaceText(firstCompiledImport, specifiersString);
}
} else {
// Import not found, add a new one
yield fixer.insertTextAfter(
Expand All @@ -104,16 +113,18 @@ const fixWrapper = (node: CSSValue, context: Context) => {
);
}

const cssFunctionName = cssImportName ?? 'css';

if (node.type === 'ObjectExpression') {
const parent = node.parent;
if (parent && parent.type === 'TSAsExpression') {
yield fixer.replaceText(parent, `css(${source.getText(node)})`);
yield fixer.replaceText(parent, `${cssFunctionName}(${source.getText(node)})`);
} else {
yield fixer.insertTextBefore(node, 'css(');
yield fixer.insertTextBefore(node, `${cssFunctionName}(`);
yield fixer.insertTextAfter(node, ')');
}
} else {
yield fixer.insertTextBefore(node, 'css');
yield fixer.insertTextBefore(node, cssFunctionName);
}
}

Expand Down
26 changes: 26 additions & 0 deletions packages/eslint-plugin/src/utils/ast-to-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,29 @@ export const buildNamedImport = (

return '';
};

/**
* Given a list of import declarations and the name of an import,
* return two things:
*
* 1. Whether the import declarations import the name `importName`, and
* 2. What name `importName` is imported as.
*
* @param declarations
* @param importName
* @returns the name that `importName` is imported as, or `undefined`
* if `importName` is not imported
*/
export const getImportedName = (
declarations: ImportDeclaration[],
importName: string
): string | undefined => {
for (const decl of declarations) {
for (const specifier of decl.specifiers) {
if (specifier.type === 'ImportSpecifier' && importName === specifier.imported.name) {
return specifier.local.name;
}
}
}
return undefined;
};

0 comments on commit 59687ab

Please sign in to comment.