From e66a6801036222b7413267e3c243721f05729196 Mon Sep 17 00:00:00 2001 From: 1aron Date: Mon, 13 Nov 2023 21:10:33 +0800 Subject: [PATCH] Perf(ESLint): Use workers to share configuration and speed up expensive style calculations --- .gitignore | 4 +- examples/eslint/package.json | 1 + .../functions/reorder-for-readable-classes.ts | 11 +- packages/eslint-plugin/package.json | 9 +- .../src/rules/class-collision.ts | 19 +-- .../eslint-plugin/src/rules/class-order.ts | 29 ++-- .../src/rules/class-validation.ts | 15 ++- .../src/utils/define-visitors.ts | 2 +- .../utils/extract-classnames-from-value.ts | 21 +++ .../src/utils/extract-range-from-node.ts | 11 ++ .../src/utils/extract-value-from-node.ts | 20 +++ packages/eslint-plugin/src/utils/find-loc.ts | 40 ++++++ .../eslint-plugin/src/utils/get-mastercss.ts | 13 ++ .../src/utils/get-template-element-body.ts | 8 ++ .../src/utils/get-template-element-prefix.ts | 8 ++ .../src/utils/get-template-element-suffix.ts | 7 + .../utils/{ast.ts => parse-node-recursive.ts} | 126 +----------------- .../src/utils/reorder-valid-classes-action.ts | 8 ++ .../src/utils/resolve-context.ts | 5 +- .../src/utils/valid-rules-action.ts | 8 ++ .../src/utils/validate-action.ts | 8 ++ .../utils/workers/reorder-valid-classes.ts | 11 ++ .../src/utils/workers/valid-rules.ts | 21 +++ .../src/utils/workers/validate.ts | 11 ++ .../eslint-plugin/tests/collision.test.ts | 13 +- packages/eslint-plugin/tests/order.test.ts | 9 +- .../eslint-plugin/tests/validation.test.ts | 36 +++++ packages/eslint-plugin/tsconfig.json | 2 +- pnpm-lock.yaml | 10 +- 29 files changed, 318 insertions(+), 168 deletions(-) create mode 100644 packages/eslint-plugin/src/utils/extract-classnames-from-value.ts create mode 100644 packages/eslint-plugin/src/utils/extract-range-from-node.ts create mode 100644 packages/eslint-plugin/src/utils/extract-value-from-node.ts create mode 100644 packages/eslint-plugin/src/utils/find-loc.ts create mode 100644 packages/eslint-plugin/src/utils/get-mastercss.ts create mode 100644 packages/eslint-plugin/src/utils/get-template-element-body.ts create mode 100644 packages/eslint-plugin/src/utils/get-template-element-prefix.ts create mode 100644 packages/eslint-plugin/src/utils/get-template-element-suffix.ts rename packages/eslint-plugin/src/utils/{ast.ts => parse-node-recursive.ts} (59%) create mode 100644 packages/eslint-plugin/src/utils/reorder-valid-classes-action.ts create mode 100644 packages/eslint-plugin/src/utils/valid-rules-action.ts create mode 100644 packages/eslint-plugin/src/utils/validate-action.ts create mode 100644 packages/eslint-plugin/src/utils/workers/reorder-valid-classes.ts create mode 100644 packages/eslint-plugin/src/utils/workers/valid-rules.ts create mode 100644 packages/eslint-plugin/src/utils/workers/validate.ts diff --git a/.gitignore b/.gitignore index fe48ad432..b248aa2c9 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,6 @@ packages/extractor/**/*master.css.* packages/extractor.vite/tests/vite/index.html packages/extractor.vite/tests/vite/master.css.* packages/extractor.webpack/tests/webpack/src/index.html -packages/extractor.webpack/tests/webpack/src/master.css.* \ No newline at end of file +packages/extractor.webpack/tests/webpack/src/master.css.* + +**/*/cache \ No newline at end of file diff --git a/examples/eslint/package.json b/examples/eslint/package.json index c1c40ef26..41796af78 100644 --- a/examples/eslint/package.json +++ b/examples/eslint/package.json @@ -3,6 +3,7 @@ "devDependencies": { "@angular-eslint/template-parser": "^16.2.0", "@master/css.react": "workspace:^", + "@master/css": "workspace:^", "@master/eslint-config-css": "workspace:^", "@master/eslint-plugin-css": "workspace:^", "@typescript-eslint/parser": "^6.9.0", diff --git a/packages/css/src/functions/reorder-for-readable-classes.ts b/packages/css/src/functions/reorder-for-readable-classes.ts index 6a3d397dd..8b4b7a0f3 100644 --- a/packages/css/src/functions/reorder-for-readable-classes.ts +++ b/packages/css/src/functions/reorder-for-readable-classes.ts @@ -5,12 +5,17 @@ import { MasterCSS } from '../core' /** * Sorts classes in a consistent order * @param classes - * @param config + * @param options * @returns consistent classes */ -export default function reorderForReadableClasses(classes: string[], config?: Config) { +export default function reorderForReadableClasses(classes: string[], options?: { css?: MasterCSS, config?: Config }) { if (!classes.length) return - const css = new MasterCSS(config) + let css: MasterCSS + if (options?.css) { + css = options?.css + } else { + css = new MasterCSS(options?.config) + } for (const eachClass of classes) { css.add(eachClass) } diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 08b24af55..5613ba8ea 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -41,17 +41,20 @@ "dependencies": { "@master/css": "workspace:^", "@master/css-validator": "workspace:^", - "explore-config": "^2.5.19" + "explore-config": "^2.5.19", + "synckit": "^0.8.5" }, "devDependencies": { "@angular-eslint/template-parser": "^16.2.0", + "@swc-node/register": "^1.6.8", "@types/node": "^20.6.0", - "@typescript-eslint/parser": "^5.50.0", - "@typescript-eslint/utils": "^6.9.0", + "@typescript-eslint/parser": "^6.10.0", + "@typescript-eslint/utils": "^6.10.0", "eslint": "^8.52.0", "eslint-plugin-markdown": "^3.0.1", "eslint-plugin-mdx": "^2.2.0", "svelte-eslint-parser": "^0.33.1", + "ts-node": "^10.9.1", "vue-eslint-parser": "^9.3.1" } } \ No newline at end of file diff --git a/packages/eslint-plugin/src/rules/class-collision.ts b/packages/eslint-plugin/src/rules/class-collision.ts index 17bf7a8df..80f755ef9 100644 --- a/packages/eslint-plugin/src/rules/class-collision.ts +++ b/packages/eslint-plugin/src/rules/class-collision.ts @@ -1,9 +1,10 @@ -import * as astUtil from '../utils/ast' import areDeclarationsEqual from '../utils/are-declarations-equal' import defineVisitors from '../utils/define-visitors' import resolveContext from '../utils/resolve-context' -import { createValidRules } from '@master/css-validator' import { Rule } from 'eslint' +import findLoc from '../utils/find-loc' +import { parseNodeRecursive } from '../utils/parse-node-recursive' +import validRulesAction from '../utils/valid-rules-action' export default { meta: { @@ -40,9 +41,9 @@ export default { ], }, create(context) { - const { options, settings, config } = resolveContext(context) + const { options, settings } = resolveContext(context) const visitNode = (node, arg = null) => { - astUtil.parseNodeRecursive( + parseNodeRecursive( node, arg, (classNames, node, originalClassNamesValue, start, end) => { @@ -50,17 +51,11 @@ export default { const sourceCodeLines = sourceCode.lines const nodeStartLine = node.loc.start.line const nodeEndLine = node.loc.end.line - const ruleOfClass = {} - classNames - .forEach(eachClassName => { - ruleOfClass[eachClassName] = createValidRules(eachClassName, { config })[0] - }) - + const ruleOfClass = validRulesAction(classNames, settings.config) for (let i = 0; i < classNames.length; i++) { const className = classNames[i] const rule = ruleOfClass[className] const conflicts = [] - if (rule) { for (let j = 0; j < classNames.length; j++) { const compareClassName = classNames[j] @@ -83,7 +78,7 @@ export default { fixClassNames = fixClassNames.replace(new RegExp(`\\s+${regexSafe}|${regexSafe}\\s+`), '') } context.report({ - loc: astUtil.findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine), + loc: findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine), messageId: 'collisionClass', data: { message: `"${className}" applies the same CSS declarations as ${conflictClassNamesMsg}.`, diff --git a/packages/eslint-plugin/src/rules/class-order.ts b/packages/eslint-plugin/src/rules/class-order.ts index ea07bba4b..e6a26cfc3 100644 --- a/packages/eslint-plugin/src/rules/class-order.ts +++ b/packages/eslint-plugin/src/rules/class-order.ts @@ -1,9 +1,15 @@ /* eslint-disable no-case-declarations */ -import * as astUtil from '../utils/ast' import defineVisitors from '../utils/define-visitors' import resolveContext from '../utils/resolve-context' -import { reorderForReadableClasses } from '@master/css' import { Rule } from 'eslint' +import getTemplateElementBody from '../utils/get-template-element-body' +import getTemplateElementSuffix from '../utils/get-template-element-suffix' +import getTemplateElementPrefix from '../utils/get-template-element-prefix' +import extractValueFromNode from '../utils/extract-value-from-node' +import extractRangeFromNode from '../utils/extract-range-from-node' +import extractClassnamesFromValue from '../utils/extract-classnames-from-value' +import findLoc from '../utils/find-loc' +import reorderValidClassesAction from '../utils/reorder-valid-classes-action' export default { meta: { @@ -40,7 +46,7 @@ export default { ], }, create: function (context) { - const { options, settings, config } = resolveContext(context) + const { options, settings } = resolveContext(context) const sourceCode = context.sourceCode const visitNode = (node, arg = null) => { @@ -53,8 +59,8 @@ export default { let expStrings = [] if (arg === null) { - originalClassNamesValue = astUtil.extractValueFromNode(node) - const range = astUtil.extractRangeFromNode(node) + originalClassNamesValue = extractValueFromNode(node) + const range = extractRangeFromNode(node) if (node.type === 'TextAttribute') { start = range[0] end = range[1] @@ -128,23 +134,22 @@ export default { // start/end does not include the backticks, therefore it matches value.raw. const txt = context.sourceCode.getText(arg) - prefix = astUtil.getTemplateElementPrefix(txt, originalClassNamesValue) - suffix = astUtil.getTemplateElementSuffix(txt, originalClassNamesValue) - originalClassNamesValue = astUtil.getTemplateElementBody(txt, prefix, suffix) + prefix = getTemplateElementPrefix(txt, originalClassNamesValue) + suffix = getTemplateElementSuffix(txt, originalClassNamesValue) + originalClassNamesValue = getTemplateElementBody(txt, prefix, suffix) break } } const { classNames, whitespaces, headSpace, tailSpace } = - astUtil.extractClassnamesFromValue(originalClassNamesValue) + extractClassnamesFromValue(originalClassNamesValue) if (classNames.length <= 1) { // Don't run sorting for a single or empty className return } - let orderedClassNames = reorderForReadableClasses(classNames, config) - .filter(eachOrderedClassName => classNames.includes(eachOrderedClassName)) + let orderedClassNames = reorderValidClassesAction(classNames, settings.config) orderedClassNames = classNames.filter(x => !orderedClassNames.includes(x)) .concat(orderedClassNames) @@ -178,7 +183,7 @@ export default { const nodeStartLine = node.loc.start.line const nodeEndLine = node.loc.end.line const descriptor = { - loc: astUtil.findLoc(originalClassNamesValue, sourceCodeLines, nodeStartLine, nodeEndLine), + loc: findLoc(originalClassNamesValue, sourceCodeLines, nodeStartLine, nodeEndLine), messageId: 'invalidClassOrder', fix: function (fixer) { return fixer.replaceTextRange([start, end], validatedClassNamesValue) diff --git a/packages/eslint-plugin/src/rules/class-validation.ts b/packages/eslint-plugin/src/rules/class-validation.ts index a9cb35877..673f610f4 100644 --- a/packages/eslint-plugin/src/rules/class-validation.ts +++ b/packages/eslint-plugin/src/rules/class-validation.ts @@ -1,8 +1,9 @@ -import * as astUtil from '../utils/ast' import defineVisitors from '../utils/define-visitors' import resolveContext from '../utils/resolve-context' -import { validate } from '@master/css-validator' import { Rule } from 'eslint' +import findLoc from '../utils/find-loc' +import { parseNodeRecursive } from '../utils/parse-node-recursive' +import validateAction from '../utils/validate-action' export default { meta: { @@ -40,9 +41,9 @@ export default { ], }, create: function (context) { - const { options, settings, config } = resolveContext(context) + const { options, settings } = resolveContext(context) const visitNode = (node, arg = null) => { - astUtil.parseNodeRecursive( + parseNodeRecursive( node, arg, (classNames, node) => { @@ -51,12 +52,12 @@ export default { const nodeStartLine = node.loc.start.line const nodeEndLine = node.loc.end.line for (const className of classNames) { - const { isMasterCSS, errors } = validate(className, { config }) + const { isMasterCSS, errors } = validateAction(className, settings.config) if (errors.length > 0) { for (const error of errors) { if (isMasterCSS) { context.report({ - loc: astUtil.findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine), + loc: findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine), messageId: 'invalidClass', data: { message: error.message + '.', @@ -64,7 +65,7 @@ export default { }) } else if (options.disallowUnknownClass) { context.report({ - loc: astUtil.findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine), + loc: findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine), messageId: 'disallowUnknownClass', data: { message: `"${className}" is not a valid or known class.` diff --git a/packages/eslint-plugin/src/utils/define-visitors.ts b/packages/eslint-plugin/src/utils/define-visitors.ts index 4a52974d8..c4e0f2670 100644 --- a/packages/eslint-plugin/src/utils/define-visitors.ts +++ b/packages/eslint-plugin/src/utils/define-visitors.ts @@ -1,4 +1,4 @@ -import type { RuleListener } from '@typescript-eslint/utils/ts-eslint' +import type { RuleListener } from '@typescript-eslint/utils/dist/ts-eslint' import { Rule } from 'eslint' export default function defineVisitors({ context, settings }: { context: Rule.RuleContext, settings: any }, visitNode) { diff --git a/packages/eslint-plugin/src/utils/extract-classnames-from-value.ts b/packages/eslint-plugin/src/utils/extract-classnames-from-value.ts new file mode 100644 index 000000000..89b764e87 --- /dev/null +++ b/packages/eslint-plugin/src/utils/extract-classnames-from-value.ts @@ -0,0 +1,21 @@ +const separatorRegEx = /([\t\n\f\r ]+)/ + +export default function extractClassnamesFromValue(classStr) { + if (typeof classStr !== 'string') { + return { classNames: [], whitespaces: [], headSpace: false, tailSpace: false } + } + const parts = classStr.split(separatorRegEx) + if (parts[0] === '') { + parts.shift() + } + if (parts[parts.length - 1] === '') { + parts.pop() + } + const headSpace = separatorRegEx.test(parts[0]) + const tailSpace = separatorRegEx.test(parts[parts.length - 1]) + const isClass = (_, i) => (headSpace ? i % 2 !== 0 : i % 2 === 0) + const isNotClass = (_, i) => (headSpace ? i % 2 === 0 : i % 2 !== 0) + const classNames = parts.filter(isClass) + const whitespaces = parts.filter(isNotClass) + return { classNames: classNames, whitespaces: whitespaces, headSpace: headSpace, tailSpace: tailSpace } +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/extract-range-from-node.ts b/packages/eslint-plugin/src/utils/extract-range-from-node.ts new file mode 100644 index 000000000..d7b0c6050 --- /dev/null +++ b/packages/eslint-plugin/src/utils/extract-range-from-node.ts @@ -0,0 +1,11 @@ +export default function extractRangeFromNode(node) { + if (node.type === 'TextAttribute' && node.name === 'class') { + return [node.valueSpan.fullStart.offset, node.valueSpan.end.offset] + } + switch (node.value.type) { + case 'JSXExpressionContainer': + return node.value.expression.range + default: + return node.value.range + } +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/extract-value-from-node.ts b/packages/eslint-plugin/src/utils/extract-value-from-node.ts new file mode 100644 index 000000000..abcb332e8 --- /dev/null +++ b/packages/eslint-plugin/src/utils/extract-value-from-node.ts @@ -0,0 +1,20 @@ + +export default function extractValueFromNode(node) { + if (node.type === 'TextAttribute' && node.name === 'class') { + return node.value + } + switch (node.value.type) { + case 'JSXExpressionContainer': + return node.value.expression.value + case 'VExpressionContainer': + switch (node.value.expression.type) { + case 'ArrayExpression': + return node.value.expression.elements + case 'ObjectExpression': + return node.value.expression.properties + } + return node.value.expression.value + default: + return node.value.value + } +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/find-loc.ts b/packages/eslint-plugin/src/utils/find-loc.ts new file mode 100644 index 000000000..7f39c6fa0 --- /dev/null +++ b/packages/eslint-plugin/src/utils/find-loc.ts @@ -0,0 +1,40 @@ + +export default function findLoc(text, lines, startLine, endLine) { + const targetLines = text.match(/.+(?:\r\n|\n)?/g) + + let checkingTargetLine = 0 + let resultStart = null + let checking = false + + for (let i = startLine; i <= endLine; i++) { + const sourceCodeLine = lines[i - 1] + + const index = sourceCodeLine.indexOf(targetLines[checkingTargetLine].replace(/\r\n|\n/, '')) + if (index !== -1) { + if (checkingTargetLine === 0) { + resultStart = { + line: i, + column: index + } + } + if (checkingTargetLine === targetLines.length - 1) { + return { + start: resultStart, + end: { + line: i, + column: index + text.length + } + } + } + checking = true + checkingTargetLine++ + } else { + if (checking) { + checking = false + checkingTargetLine = 0 + resultStart = null + } + } + } + return null +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/get-mastercss.ts b/packages/eslint-plugin/src/utils/get-mastercss.ts new file mode 100644 index 000000000..edd1155ce --- /dev/null +++ b/packages/eslint-plugin/src/utils/get-mastercss.ts @@ -0,0 +1,13 @@ +import MasterCSS, { Config, config } from '@master/css' +import exploreConfig from 'explore-config' + +let currentCSS: MasterCSS +let currentConfig: string | Config + +export default function getMasterCSS(config: string | Config) { + if (!currentCSS || currentConfig !== config) { + currentCSS = new MasterCSS(typeof config === 'object' ? config : exploreConfig(config || '')) + currentConfig = config + } + return currentCSS +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/get-template-element-body.ts b/packages/eslint-plugin/src/utils/get-template-element-body.ts new file mode 100644 index 000000000..67b6d9bfc --- /dev/null +++ b/packages/eslint-plugin/src/utils/get-template-element-body.ts @@ -0,0 +1,8 @@ +export default function getTemplateElementBody(text, prefix, suffix) { + let arr = text.split(prefix) + arr.shift() + const body = arr.join(prefix) + arr = body.split(suffix) + arr.pop() + return arr.join(suffix) +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/get-template-element-prefix.ts b/packages/eslint-plugin/src/utils/get-template-element-prefix.ts new file mode 100644 index 000000000..24a41762b --- /dev/null +++ b/packages/eslint-plugin/src/utils/get-template-element-prefix.ts @@ -0,0 +1,8 @@ + +export default function getTemplateElementPrefix(text, raw) { + const idx = text.indexOf(raw) + if (idx === 0) { + return '' + } + return text.split(raw).shift() +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/get-template-element-suffix.ts b/packages/eslint-plugin/src/utils/get-template-element-suffix.ts new file mode 100644 index 000000000..2e0455b5b --- /dev/null +++ b/packages/eslint-plugin/src/utils/get-template-element-suffix.ts @@ -0,0 +1,7 @@ + +export default function getTemplateElementSuffix(text, raw) { + if (text.indexOf(raw) === -1) { + return '' + } + return text.split(raw).pop() +} \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/ast.ts b/packages/eslint-plugin/src/utils/parse-node-recursive.ts similarity index 59% rename from packages/eslint-plugin/src/utils/ast.ts rename to packages/eslint-plugin/src/utils/parse-node-recursive.ts index 21d08173f..470c41d94 100644 --- a/packages/eslint-plugin/src/utils/ast.ts +++ b/packages/eslint-plugin/src/utils/parse-node-recursive.ts @@ -1,60 +1,9 @@ -/* eslint-disable no-case-declarations */ import { Rule } from 'eslint' - -const separatorRegEx = /([\t\n\f\r ]+)/ - -export function extractRangeFromNode(node) { - if (node.type === 'TextAttribute' && node.name === 'class') { - return [node.valueSpan.fullStart.offset, node.valueSpan.end.offset] - } - switch (node.value.type) { - case 'JSXExpressionContainer': - return node.value.expression.range - default: - return node.value.range - } -} - -export function extractValueFromNode(node) { - if (node.type === 'TextAttribute' && node.name === 'class') { - return node.value - } - switch (node.value.type) { - case 'JSXExpressionContainer': - return node.value.expression.value - case 'VExpressionContainer': - switch (node.value.expression.type) { - case 'ArrayExpression': - return node.value.expression.elements - case 'ObjectExpression': - return node.value.expression.properties - } - return node.value.expression.value - default: - return node.value.value - } -} - -export function extractClassnamesFromValue(classStr) { - if (typeof classStr !== 'string') { - return { classNames: [], whitespaces: [], headSpace: false, tailSpace: false } - } - const parts = classStr.split(separatorRegEx) - if (parts[0] === '') { - parts.shift() - } - if (parts[parts.length - 1] === '') { - parts.pop() - } - const headSpace = separatorRegEx.test(parts[0]) - const tailSpace = separatorRegEx.test(parts[parts.length - 1]) - const isClass = (_, i) => (headSpace ? i % 2 !== 0 : i % 2 === 0) - const isNotClass = (_, i) => (headSpace ? i % 2 === 0 : i % 2 !== 0) - const classNames = parts.filter(isClass) - const whitespaces = parts.filter(isNotClass) - return { classNames: classNames, whitespaces: whitespaces, headSpace: headSpace, tailSpace: tailSpace } -} - +import getTemplateElementPrefix from './get-template-element-prefix' +import getTemplateElementSuffix from './get-template-element-suffix' +import extractValueFromNode from './extract-value-from-node' +import extractClassnamesFromValue from './extract-classnames-from-value' +import extractRangeFromNode from './extract-range-from-node' /** * Inspect and parse an abstract syntax node and run a callback function @@ -164,6 +113,7 @@ export function parseNodeRecursive(rootNode, childNode, cb, skipConditional = fa originalClassNamesValue = childNode.value.raw start = childNode.range[0] end = childNode.range[1] + // eslint-disable-next-line no-case-declarations const txt = context?.sourceCode.getText(childNode) ?? '' prefix = getTemplateElementPrefix(txt, originalClassNamesValue) suffix = getTemplateElementSuffix(txt, originalClassNamesValue) @@ -178,68 +128,4 @@ export function parseNodeRecursive(rootNode, childNode, cb, skipConditional = fa const targetNode = isolate ? null : rootNode cb(classNames, targetNode, originalClassNamesValue, start, end, prefix, suffix) } -} - -export function getTemplateElementPrefix(text, raw) { - const idx = text.indexOf(raw) - if (idx === 0) { - return '' - } - return text.split(raw).shift() -} - -export function getTemplateElementSuffix(text, raw) { - if (text.indexOf(raw) === -1) { - return '' - } - return text.split(raw).pop() -} - -export function getTemplateElementBody(text, prefix, suffix) { - let arr = text.split(prefix) - arr.shift() - const body = arr.join(prefix) - arr = body.split(suffix) - arr.pop() - return arr.join(suffix) -} - -export function findLoc(text, lines, startLine, endLine) { - const targetLines = text.match(/.+(?:\r\n|\n)?/g) - - let checkingTargetLine = 0 - let resultStart = null - let checking = false - - for (let i = startLine; i <= endLine; i++) { - const sourceCodeLine = lines[i - 1] - - const index = sourceCodeLine.indexOf(targetLines[checkingTargetLine].replace(/\r\n|\n/, '')) - if (index !== -1) { - if (checkingTargetLine === 0) { - resultStart = { - line: i, - column: index - } - } - if (checkingTargetLine === targetLines.length - 1) { - return { - start: resultStart, - end: { - line: i, - column: index + text.length - } - } - } - checking = true - checkingTargetLine++ - } else { - if (checking) { - checking = false - checkingTargetLine = 0 - resultStart = null - } - } - } - return null } \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/reorder-valid-classes-action.ts b/packages/eslint-plugin/src/utils/reorder-valid-classes-action.ts new file mode 100644 index 000000000..139f9a60c --- /dev/null +++ b/packages/eslint-plugin/src/utils/reorder-valid-classes-action.ts @@ -0,0 +1,8 @@ +import { createSyncFn } from 'synckit' +import type { runReorderValidClasses } from './workers/reorder-valid-classes' + +const reorderValidClassesAction = createSyncFn(require.resolve('./workers/reorder-valid-classes'), { + tsRunner: 'swc' +}) as typeof runReorderValidClasses + +export default reorderValidClassesAction \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/resolve-context.ts b/packages/eslint-plugin/src/utils/resolve-context.ts index 3a18c9f61..4296ecee0 100644 --- a/packages/eslint-plugin/src/utils/resolve-context.ts +++ b/packages/eslint-plugin/src/utils/resolve-context.ts @@ -1,12 +1,9 @@ import settings from '../settings' -import exploreConfig from 'explore-config' export default function resolveContext(context) { const resolvedSettings = Object.assign(settings, context.settings?.['@master/css']) - const config = resolvedSettings?.config return { settings: resolvedSettings, - options: context.options[0] || {}, - config: typeof config === 'object' ? config : exploreConfig(resolvedSettings?.config || '') + options: context.options[0] || {} } } \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/valid-rules-action.ts b/packages/eslint-plugin/src/utils/valid-rules-action.ts new file mode 100644 index 000000000..69ad17b62 --- /dev/null +++ b/packages/eslint-plugin/src/utils/valid-rules-action.ts @@ -0,0 +1,8 @@ +import { createSyncFn } from 'synckit' +import type { runValidRules } from './workers/valid-rules' + +const validRulesAction = createSyncFn(require.resolve('./workers/valid-rules'), { + tsRunner: 'swc' +}) as typeof runValidRules + +export default validRulesAction \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/validate-action.ts b/packages/eslint-plugin/src/utils/validate-action.ts new file mode 100644 index 000000000..1656149bf --- /dev/null +++ b/packages/eslint-plugin/src/utils/validate-action.ts @@ -0,0 +1,8 @@ +import { createSyncFn } from 'synckit' +import type { runValidate } from './workers/validate' + +const validateAction = createSyncFn(require.resolve('./workers/validate'), { + tsRunner: 'swc' +}) as typeof runValidate + +export default validateAction \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/workers/reorder-valid-classes.ts b/packages/eslint-plugin/src/utils/workers/reorder-valid-classes.ts new file mode 100644 index 000000000..1991b4232 --- /dev/null +++ b/packages/eslint-plugin/src/utils/workers/reorder-valid-classes.ts @@ -0,0 +1,11 @@ +import { Config, reorderForReadableClasses } from '@master/css' +import { runAsWorker } from 'synckit' +import getMasterCSS from '../get-mastercss' + +export function runReorderValidClasses(classNames: string[], config: string | Config): string[] { + const currentCSS = getMasterCSS(config) + return reorderForReadableClasses(classNames, { css: currentCSS }) + .filter((eachOrderedClassName: string) => classNames.includes(eachOrderedClassName)) +} + +runAsWorker(runReorderValidClasses as any) \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/workers/valid-rules.ts b/packages/eslint-plugin/src/utils/workers/valid-rules.ts new file mode 100644 index 000000000..eb5292302 --- /dev/null +++ b/packages/eslint-plugin/src/utils/workers/valid-rules.ts @@ -0,0 +1,21 @@ +import { Config, Rule } from '@master/css' +import { runAsWorker } from 'synckit' +import getMasterCSS from '../get-mastercss' +import { createValidRules } from '@master/css-validator' + +export function runValidRules(classNames: string[], config: string | Config): Rule { + const currentCSS = getMasterCSS(config) + const ruleOfClass: any = {} + classNames + .forEach(eachClassName => { + const validRule = createValidRules(eachClassName, { css: currentCSS })[0] + if (validRule) + ruleOfClass[eachClassName] = { + declarations: validRule?.declarations, + stateToken: validRule?.stateToken, + } + }) + return ruleOfClass +} + +runAsWorker(runValidRules as any) \ No newline at end of file diff --git a/packages/eslint-plugin/src/utils/workers/validate.ts b/packages/eslint-plugin/src/utils/workers/validate.ts new file mode 100644 index 000000000..830aec995 --- /dev/null +++ b/packages/eslint-plugin/src/utils/workers/validate.ts @@ -0,0 +1,11 @@ +import { Config } from '@master/css' +import { runAsWorker } from 'synckit' +import getMasterCSS from '../get-mastercss' +import { validate } from '@master/css-validator' + +export function runValidate(className: string, config: string | Config): { isMasterCSS: boolean; errors: SyntaxError[] } { + const currentCSS = getMasterCSS(config) + return validate(className, { css: currentCSS }) as any +} + +runAsWorker(runValidate as any) \ No newline at end of file diff --git a/packages/eslint-plugin/tests/collision.test.ts b/packages/eslint-plugin/tests/collision.test.ts index 55a09de53..49dac9a24 100644 --- a/packages/eslint-plugin/tests/collision.test.ts +++ b/packages/eslint-plugin/tests/collision.test.ts @@ -11,7 +11,10 @@ new RuleTester({ valid: [ { code: `
Simple, basic
`, - } + }, + { + code: `
Error class
`, + }, ], invalid: [ { @@ -21,6 +24,14 @@ new RuleTester({ { messageId: 'collisionClass' }, { messageId: 'collisionClass' } ] + }, + { + code: `
Error class
`, + output: `
Error class
`, + errors: [ + { messageId: 'collisionClass' }, + { messageId: 'collisionClass' } + ] } ] }) diff --git a/packages/eslint-plugin/tests/order.test.ts b/packages/eslint-plugin/tests/order.test.ts index 4f8514007..bb3ada046 100644 --- a/packages/eslint-plugin/tests/order.test.ts +++ b/packages/eslint-plugin/tests/order.test.ts @@ -100,7 +100,6 @@ new RuleTester({ { code: `
Collision class
`, }, - { code: ` export default () => ( @@ -122,6 +121,9 @@ new RuleTester({ ) `, parser: require.resolve('@typescript-eslint/parser') + }, + { + code: `
Error class
`, } ], invalid: [ @@ -466,6 +468,11 @@ new RuleTester({ code: `
Template
`, output: `
Template
`, errors: [{ messageId: 'invalidClassOrder' }], + }, + { + code: `
Error class
`, + output: `
Error class
`, + errors: [{ messageId: 'invalidClassOrder' }], } ], }) \ No newline at end of file diff --git a/packages/eslint-plugin/tests/validation.test.ts b/packages/eslint-plugin/tests/validation.test.ts index d1a3483b9..466a5f909 100644 --- a/packages/eslint-plugin/tests/validation.test.ts +++ b/packages/eslint-plugin/tests/validation.test.ts @@ -31,6 +31,27 @@ new RuleTester({ errors: [ { messageId: 'invalidClass' }, ] + }, + { + code: `
Error class
`, + errors: [ + { messageId: 'disallowUnknownClass' }, + { messageId: 'disallowUnknownClass' }, + { messageId: 'disallowUnknownClass' }, + { messageId: 'disallowUnknownClass' }, + { messageId: 'invalidClass' } + ], + options: [ + { + disallowUnknownClass: true + } + ] + }, + { + code: `
Error class
`, + errors: [ + { messageId: 'invalidClass' } + ] } ] }) @@ -63,5 +84,20 @@ new RuleTester({ } ] }, + { + code: `
Error class
`, + errors: [ + { messageId: 'disallowUnknownClass' }, + { messageId: 'disallowUnknownClass' }, + { messageId: 'disallowUnknownClass' }, + { messageId: 'disallowUnknownClass' }, + { messageId: 'invalidClass' } + ], + options: [ + { + disallowUnknownClass: true + } + ] + }, ], }) \ No newline at end of file diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index 91da3c93b..c338512b3 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "baseUrl": ".", "rootDir": "src", - "moduleResolution": "Bundler" + "moduleResolution": "Node" }, "include": [ "src/**/*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b7ee3eaf..7cb797be0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1348,6 +1348,9 @@ importers: explore-config: specifier: ^2.5.19 version: 2.5.19 + synckit: + specifier: ^0.8.5 + version: 0.8.5 devDependencies: '@angular-eslint/template-parser': specifier: ^16.2.0 @@ -1356,8 +1359,8 @@ importers: specifier: ^20.6.0 version: 20.9.0 '@typescript-eslint/parser': - specifier: ^5.50.0 - version: 5.62.0(eslint@8.53.0)(typescript@5.2.2) + specifier: ^6.10.0 + version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/utils': specifier: ^6.9.0 version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) @@ -1373,6 +1376,9 @@ importers: svelte-eslint-parser: specifier: ^0.33.1 version: 0.33.1 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@swc/core@1.3.96)(@types/node@20.8.10)(typescript@5.2.2) vue-eslint-parser: specifier: ^9.3.1 version: 9.3.2(eslint@8.53.0)