Skip to content

Commit

Permalink
Merge pull request #109 from petertriho/better-headwind-classregex
Browse files Browse the repository at this point in the history
Add better class regex for javascript/javascriptreact/typescript/typescriptreact
  • Loading branch information
praveenperera authored Feb 22, 2021
2 parents 542c1cd + 025ee10 commit a579f51
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 39 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5018,10 +5018,10 @@
"default": {
"html": "\\bclass\\s*=\\s*[\\\"\\']([_a-zA-Z0-9\\s\\-\\:\\/]+)[\\\"\\']",
"css": "\\B@apply\\s+([_a-zA-Z0-9\\s\\-\\:\\/]+);",
"javascript": "(?:\\bclassName\\s*=\\s*[\\\"\\']([_a-zA-Z0-9\\s\\-\\:\\/]+)[\\\"\\'])|(?:\\btw\\s*`([_a-zA-Z0-9\\s\\-\\:\\/]*)`)",
"javascriptreact": "(?:\\bclassName\\s*=\\s*[\\\"\\']([_a-zA-Z0-9\\s\\-\\:\\/]+)[\\\"\\'])|(?:\\btw\\s*`([_a-zA-Z0-9\\s\\-\\:\\/]*)`)",
"typescript": "(?:\\bclassName\\s*=\\s*[\\\"\\']([_a-zA-Z0-9\\s\\-\\:\\/]+)[\\\"\\'])|(?:\\btw\\s*`([_a-zA-Z0-9\\s\\-\\:\\/]*)`)",
"typescriptreact": "(?:\\bclassName\\s*=\\s*[\\\"\\']([_a-zA-Z0-9\\s\\-\\:\\/]+)[\\\"\\'])|(?:\\btw\\s*`([_a-zA-Z0-9\\s\\-\\:\\/]*)`)"
"javascript": "(?:\\bclass(?:Name)?\\s*=[\\w\\d\\s_,{}\\(\\)\\[\\]]*[\"'`]([\\w\\d\\s_\\-\\:\\/${}]+)[\"'`][\\w\\d\\s_,{}\\(\\)\\[\\]]*)|(?:\\btw\\s*`([\\w\\d\\s_\\-\\:\\/]*)`)",
"javascriptreact": "(?:\\bclass(?:Name)?\\s*=[\\w\\d\\s_,{}\\(\\)\\[\\]]*[\"'`]([\\w\\d\\s_\\-\\:\\/${}]+)[\"'`][\\w\\d\\s_,{}\\(\\)\\[\\]]*)|(?:\\btw\\s*`([\\w\\d\\s_\\-\\:\\/]*)`)",
"typescript": "(?:\\bclass(?:Name)?\\s*=[\\w\\d\\s_,{}\\(\\)\\[\\]]*[\"'`]([\\w\\d\\s_\\-\\:\\/${}]+)[\"'`][\\w\\d\\s_,{}\\(\\)\\[\\]]*)|(?:\\btw\\s*`([\\w\\d\\s_\\-\\:\\/]*)`)",
"typescriptreact": "(?:\\bclass(?:Name)?\\s*=[\\w\\d\\s_,{}\\(\\)\\[\\]]*[\"'`]([\\w\\d\\s_\\-\\:\\/${}]+)[\"'`][\\w\\d\\s_,{}\\(\\)\\[\\]]*)|(?:\\btw\\s*`([\\w\\d\\s_\\-\\:\\/]*)`)"
},
"description": "An object with language IDs as keys and their values determining the regex to search for Tailwind CSS classes.",
"scope": "window"
Expand Down
64 changes: 30 additions & 34 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

import { commands, workspace, ExtensionContext, Range, window } from 'vscode';
import { sortClassString } from './utils';
import { sortClassString, getClassMatch } from './utils';
import { spawn } from 'child_process';
import { rustyWindPath } from 'rustywind';

Expand Down Expand Up @@ -39,39 +39,35 @@ export function activate(context: ExtensionContext) {
const editorText = editor.document.getText();
const editorLangId = editor.document.languageId;

const classWrapperRegex = new RegExp(configRegex[editorLangId] || configRegex['html'], 'gi');
let classWrapper: RegExpExecArray | null;
while (
(classWrapper = classWrapperRegex.exec(editorText)) !== null
) {
const wrapperMatch = classWrapper[0];
const valueMatchIndex = classWrapper.findIndex((match, idx) => idx !== 0 && match);
const valueMatch = classWrapper[valueMatchIndex];

const startPosition =
classWrapper.index + wrapperMatch.lastIndexOf(valueMatch);
const endPosition = startPosition + valueMatch.length;

const range = new Range(
editor.document.positionAt(startPosition),
editor.document.positionAt(endPosition)
);

const options = {
shouldRemoveDuplicates,
shouldPrependCustomClasses,
customTailwindPrefix
};

edit.replace(
range,
sortClassString(
valueMatch,
Array.isArray(sortOrder) ? sortOrder : [],
options
)
);
}
getClassMatch(
configRegex[editorLangId] || configRegex['html'],
editorText,
(classWrapper, wrapperMatch, valueMatch) => {
const startPosition =
classWrapper.index + wrapperMatch.lastIndexOf(valueMatch);
const endPosition = startPosition + valueMatch.length;

const range = new Range(
editor.document.positionAt(startPosition),
editor.document.positionAt(endPosition)
);

const options = {
shouldRemoveDuplicates,
shouldPrependCustomClasses,
customTailwindPrefix,
};

edit.replace(
range,
sortClassString(
valueMatch,
Array.isArray(sortOrder) ? sortOrder : [],
options
)
);
}
);
}
);

Expand Down
22 changes: 22 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,25 @@ const sortClassArray = (
const removeDuplicates = (classArray: string[]): string[] => [
...new Set(classArray)
];

export function getClassMatch(
regex: string,
editorText: string,
callback: (
classWrapper: RegExpExecArray,
wrapperMatch: string,
valueMatch: string
) => void
) {
const classWrapperRegex = new RegExp(regex, 'gi');
let classWrapper: RegExpExecArray | null;
while ((classWrapper = classWrapperRegex.exec(editorText)) !== null) {
const wrapperMatch = classWrapper[0];
const valueMatchIndex = classWrapper.findIndex(
(match, idx) => idx !== 0 && match
);
const valueMatch = classWrapper[valueMatchIndex];

callback(classWrapper, wrapperMatch, valueMatch);
}
}
171 changes: 170 additions & 1 deletion tests/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sortClassString } from '../src/utils';
import { sortClassString, getClassMatch } from '../src/utils';
import 'jest';
import * as _ from 'lodash';

Expand Down Expand Up @@ -76,3 +76,172 @@ describe('removeDuplicates', () => {
);
});
});

describe('extract className (jsx) string', () => {
const configRegex: { [key: string]: string } =
pjson.contributes.configuration[0].properties['headwind.classRegex']
.default;

const jsxLanguages = [
'javascript',
'javascriptreact',
'typescript',
'typescriptreact',
];

const classString = 'w-64 h-full bg-blue-400 relative';

const generateEditorText = (classNameString: string) => `
export const Layout = ({ children }) => (
<div>
<div className=${classNameString}></div>
<div>{children}</div>
</div>
)`;

const multiLineClassString = `
w-64
h-full
bg-blue-400
relative
`;
it.each([
[
'simple single quotes',
generateEditorText(`'${classString}'`),
classString,
],
[
'simple double quotes',
generateEditorText(`"${classString}"`),
classString,
],
[
'curly braces around single quotes',
generateEditorText(`{ '${classString}' }`),
classString,
],
[
'curly braces around double quotes',
generateEditorText(`{ "${classString}" }`),
classString,
],
[
'simple clsx single quotes',
generateEditorText(`{ clsx('${classString}' }`),
classString,
],
[
'simple clsx double quotes',
generateEditorText(`{ clsx("${classString}" }`),
classString,
],
[
'simple classname single quotes',
generateEditorText(`{ classname('${classString}' }`),
classString,
],
[
'simple classname double quotes',
generateEditorText(`{ classname("${classString}" }`),
classString,
],
[
'simple foo func single quotes',
generateEditorText(`{ foo('${classString}' }`),
classString,
],
[
'simple foo func double quotes',
generateEditorText(`{ foo("${classString}" }`),
classString,
],
[
'foo func multi str single quotes (only extracts first string)',
generateEditorText(`{ foo('${classString}', 'class1 class2' }`),
classString,
],
[
'foo func multi str double quotes (only extracts first string)',
generateEditorText(`{ foo("${classString}", "class1, class2" }`),
classString,
],
[
'foo func multi var single quotes',
generateEditorText(`{ clsx(foo, bar, '${classString}', foo, bar }`),
classString,
],
[
'foo func multi var double quotes',
generateEditorText(`{ clsx(foo, bar, "${classString}", foo, bar }`),
classString,
],
[
'foo func multi var multi str single quotes',
generateEditorText(
`{ clsx(foo, bar, '${classString}', foo, 'class1 class2', bar }`
),
classString,
],
[
'foo func multi var multi str double quotes',
generateEditorText(
`{ clsx(foo, bar, "${classString}", foo, "class1 class2", bar }`
),
classString,
],
[
'complex foo func single quotes multi lines',
generateEditorText(`
{ clsx(
foo,
bar,
'${classString}',
foo,
'class1 class2',
bar
}`),
classString,
],
[
'simple multi line double quotes',
generateEditorText(multiLineClassString),
multiLineClassString,
],
[
'complex foo func double quotes multi lines',
generateEditorText(`
{ clsx(
foo,
bar,
"${classString}",
foo,
"class1 class2",
bar
}`),
classString,
],
[
'class attribute',
`class="${classString}"`,
classString
],
[
'string literal',
`export function FormGroup({className = '', ...props}) {
return <div className={\`${classString} \$\{className\}\`} {...props} />
}`,
`${classString} \$\{className\}`
]
])('%s', (testName, editorText, expectedValueMatch) => {
for (const jsxLanguage of jsxLanguages) {
getClassMatch(
configRegex[jsxLanguage],
editorText,
(classWrapper, wrapperMatch, valueMatch) => {
expect(valueMatch).toBe(expectedValueMatch);
}
);
}
});
});

0 comments on commit a579f51

Please sign in to comment.