diff --git a/.eslintrc.js b/.eslintrc.js index 3702bca..c3885f1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,6 +13,7 @@ module.exports = { "plugin:@ota-meshi/+json", "plugin:@ota-meshi/+yaml", "plugin:@ota-meshi/+prettier", + "plugin:node-dependencies/recommended", ], rules: { "require-jsdoc": "off", @@ -35,10 +36,59 @@ module.exports = { "error", ["name", "filename", "settings", "options", "code"], ], + // Repo rule + "@typescript-eslint/no-restricted-imports": [ + "error", + { + patterns: [ + { + group: ["/regexpp", "/regexpp/*"], + message: "Please use `@eslint-community/regexpp` instead.", + }, + { + group: ["/eslint-utils", "/eslint-utils/*"], + message: "Please use `@eslint-community/eslint-utils` instead.", + }, + ], + }, + ], + "no-restricted-properties": [ + "error", + { + object: "context", + property: "getSourceCode", + message: "Use src/utils/compat.ts", + }, + { + object: "context", + property: "getFilename", + message: "Use src/utils/compat.ts", + }, + { + object: "context", + property: "getPhysicalFilename", + message: "Use src/utils/compat.ts", + }, + { + object: "context", + property: "getCwd", + message: "Use src/utils/compat.ts", + }, + { + object: "context", + property: "getScope", + message: "Use src/utils/compat.ts", + }, + { + object: "context", + property: "parserServices", + message: "Use src/utils/compat.ts", + }, + ], }, overrides: [ { - files: ["*.ts"], + files: ["*.ts", "*.mts"], parser: "@typescript-eslint/parser", rules: { "@typescript-eslint/no-require-imports": "off", @@ -98,5 +148,13 @@ module.exports = { "no-console": "off", }, }, + { + files: ["lib/utils/**/*.js"], + rules: { + "no-shadow": "off", + "default-case": "off", + "no-restricted-properties": "off", + }, + }, ], }; diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a22bcd8..c925a7b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,6 +1,6 @@ name: CI -on: +on: workflow_dispatch: pull_request: branches: @@ -9,44 +9,85 @@ on: branches: - main tags: - - "v*.*.*" + - "v*.*.*" jobs: eslint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: latest + + - name: Install pnpm + uses: pnpm/action-setup@v3.0.0 with: - node-version: 16 - cache: 'npm' - - run: npm ci - - run: npm run lint + version: 8 + + - name: Install + run: pnpm install --no-frozen-lockfile + + - name: Lint + run: pnpm run lint tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: latest + + - name: Install pnpm + uses: pnpm/action-setup@v3.0.0 with: - node-version: 16 - cache: 'npm' - - run: npm ci - - run: npm test + version: 8 + + - name: Install packages + run: pnpm install --no-frozen-lockfile + + - name: Test + run: pnpm run test publish: runs-on: ubuntu-latest needs: [eslint, tests] if: startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: latest + + - name: Install pnpm + uses: pnpm/action-setup@v3.0.0 with: - node-version: 16 - cache: 'npm' - - run: npm ci - - run: npm run build - - uses: JS-DevTools/npm-publish@v3 + version: 8 + + - name: Install packages + run: pnpm install --no-frozen-lockfile + + - name: Build + run: pnpm run build + + - name: Generate changelog + run: npx changelogithub + env: + GITHUB_TOKEN: ${{secrets.GH_TOKEN}} + + - name: Publish package + uses: JS-DevTools/npm-publish@v3 with: - token: ${{ secrets.NPM_TOKEN }} - - uses: softprops/action-gh-release@v1 + token: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/lib/rules/no-convention-violation.ts b/lib/rules/no-convention-violation.ts index cc61702..345d870 100644 --- a/lib/rules/no-convention-violation.ts +++ b/lib/rules/no-convention-violation.ts @@ -21,6 +21,7 @@ import type { } from "../types"; import { getResolvedSelectors } from "../styles/selectors"; import { isDefined } from "../utils/utils"; +import { getFilename } from "../utils/compat"; import type { ValidStyleContext } from "../styles/context"; export = { @@ -341,7 +342,7 @@ export = { } function getPrefix(style: ValidStyleContext): string { - const fileName = context.getFilename(); + const fileName = getFilename(context); const { dir, name } = path.parse(fileName); const defaultComponentName = diff --git a/lib/rules/no-dynamic-class-names.ts b/lib/rules/no-dynamic-class-names.ts index ead47b7..1ebf8aa 100644 --- a/lib/rules/no-dynamic-class-names.ts +++ b/lib/rules/no-dynamic-class-names.ts @@ -11,6 +11,7 @@ import type { import * as utils from "../utils/vue"; import { getClassAttrNameRegexp } from "../utils/class-attr"; +import { getSourceCode } from "../utils/compat"; import type { RuleContext, RuleListener } from "../types"; function withProps( @@ -26,11 +27,15 @@ function withProps( } return utils.compositingVisitors( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- prevent test failing + // @ts-ignore -- eslint compat types diff, fix after upgrading to eslint v9 utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(_, props) { propNames.push(...props.map((p) => p.propName).filter(isString)); }, }) as RuleListener, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- prevent test failing + // @ts-ignore -- eslint compat types diff, fix after upgrading to eslint v9 utils.defineVueVisitor(context, { onVueObjectEnter(node) { const props = utils.getComponentPropsFromOptions(node); @@ -69,7 +74,9 @@ export = { type: "problem", }, create(context: RuleContext): RuleListener { - if (!context.parserServices.defineTemplateBodyVisitor) return {}; + const sourceCode = getSourceCode(context); + + if (!sourceCode.parserServices.defineTemplateBodyVisitor) return {}; const classAttrRegexp = getClassAttrNameRegexp(context); const allowConditional = context.options[0]?.allowConditional || false; @@ -227,7 +234,7 @@ export = { } return withProps(context, (props) => - context.parserServices.defineTemplateBodyVisitor({ + sourceCode.parserServices.defineTemplateBodyVisitor({ [`VAttribute[key.name=${classAttrRegexp}]`](node: VAttribute) { reportDynamic(node, props); }, diff --git a/lib/rules/no-undefined-class-names.ts b/lib/rules/no-undefined-class-names.ts index f5f8649..90c8d04 100644 --- a/lib/rules/no-undefined-class-names.ts +++ b/lib/rules/no-undefined-class-names.ts @@ -27,6 +27,7 @@ import { isSatisfyList, } from "../utils/utils"; import { getClassAttrNameRegexp, getAttrName } from "../utils/class-attr"; +import { getSourceCode } from "../utils/compat"; function getAllSelectorsFromStyles( styles: ValidStyleContext[], @@ -98,7 +99,7 @@ function getClassPosition( let startColumn = -1; if (isMultiline) { - const classLineIndex = lines.findIndex((l) => l.includes(className))!; + const classLineIndex = lines.findIndex((l) => l.includes(className)); const classLine = lines[classLineIndex]; line = classLineIndex; @@ -244,7 +245,7 @@ export = { return {}; } const styles = getStyleContexts(context).filter(isValidStyleContext); - const source = context.getSourceCode(); + const sourceCode = getSourceCode(context); const reporter = getCommentDirectivesReporter(context); const classAttrRegexp = getClassAttrNameRegexp(context); @@ -291,8 +292,8 @@ export = { column: endCol!, }; - const startIndex = source.getIndexFromLoc(start); - const endIndex = source.getIndexFromLoc(end); + const startIndex = sourceCode.getIndexFromLoc(start); + const endIndex = sourceCode.getIndexFromLoc(end); const allAvailableClasses = className.includes("--") ? Array.from(vkcnClassSelectors.keys()) @@ -422,7 +423,7 @@ export = { return false; } - return context.parserServices.defineTemplateBodyVisitor({ + return sourceCode.parserServices.defineTemplateBodyVisitor({ [`VAttribute[key.name=${classAttrRegexpAsString}]`](attr: VAttribute) { const classListString = attr.value?.value; if (classListString === undefined) return; diff --git a/lib/styles/context/index.ts b/lib/styles/context/index.ts index a1c6f3a..85584d1 100644 --- a/lib/styles/context/index.ts +++ b/lib/styles/context/index.ts @@ -1,9 +1,9 @@ import { createStyleContexts, - StyleContext, + type StyleContext, isValidStyleContext, - ValidStyleContext, - InvalidStyleContext, + type ValidStyleContext, + type InvalidStyleContext, } from "./style"; import type { CommentDirectives } from "./comment-directive"; import { @@ -17,6 +17,7 @@ import { VueComponentContext, createVueComponentContext, } from "./vue-components"; +import { getSourceCode } from "../../utils/compat"; type CacheValue = { styles?: StyleContext[]; @@ -32,7 +33,7 @@ const CACHE = new WeakMap(); * Gets the cache. */ function getCache(context: RuleContext): CacheValue { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const { ast } = sourceCode; if (CACHE.has(ast)) { return CACHE.get(ast) as CacheValue; diff --git a/lib/styles/context/style/index.ts b/lib/styles/context/style/index.ts index f50bb59..6f52553 100644 --- a/lib/styles/context/style/index.ts +++ b/lib/styles/context/style/index.ts @@ -7,6 +7,7 @@ import type { } from "../../../types"; import type { VCSSStyleSheet, VCSSNode, VCSSSelectorNode } from "../../ast"; import { isVCSSContainerNode, hasSelectorNodes } from "../../utils/css-nodes"; +import { getSourceCode } from "../../../utils/compat"; /** * Check whether the program has invalid EOF or not. @@ -18,16 +19,17 @@ function getInvalidEOFError( inDocumentFragment: boolean; error: AST.ParseError; } | null { - const node = context.getSourceCode().ast; + const node = getSourceCode(context).ast; const body = node.templateBody; let errors = body?.errors; let inDocumentFragment = false; if (errors == null) { + const sourceCode = getSourceCode(context); /* istanbul ignore if */ - if (!context.parserServices.getDocumentFragment) { + if (!sourceCode.parserServices.getDocumentFragment) { return null; } - const df = context.parserServices.getDocumentFragment(); + const df = sourceCode.parserServices.getDocumentFragment(); inDocumentFragment = true; errors = df?.errors; /* istanbul ignore if */ @@ -62,11 +64,11 @@ function getInvalidEOFError( */ function getStyleElements(context: RuleContext): AST.VElement[] { let document: AST.VDocumentFragment | null = null; - if (context.parserServices.getDocumentFragment) { + const sourceCode = getSourceCode(context); + if (sourceCode.parserServices.getDocumentFragment) { // vue-eslint-parser v7.0.0 - document = context.parserServices.getDocumentFragment(); + document = sourceCode.parserServices.getDocumentFragment(); } else { - const sourceCode = context.getSourceCode(); const { ast } = sourceCode; const templateBody = ast.templateBody as AST.ESLintProgram | undefined; /* istanbul ignore if */ @@ -189,7 +191,7 @@ export class StyleContextImpl { public readonly cssNode: VCSSStyleSheet | null; public constructor(style: AST.VElement, context: RuleContext) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); this.styleElement = style; this.sourceCode = sourceCode; diff --git a/lib/styles/context/vue-components/find-vue.ts b/lib/styles/context/vue-components/find-vue.ts index 5d61c56..32ec6e7 100644 --- a/lib/styles/context/vue-components/find-vue.ts +++ b/lib/styles/context/vue-components/find-vue.ts @@ -1,6 +1,7 @@ import { AST } from "vue-eslint-parser"; import type { ASTNode, RuleContext } from "../../../types"; import { unwrapTypesExpression } from "../../utils/nodes"; +import { getSourceCode } from "../../../utils/compat"; const traverseNodes = AST.traverseNodes; @@ -73,7 +74,7 @@ function findVueComponent( return cached.component; } - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const componentComments = sourceCode .getAllComments() .filter((comment) => comment.value.includes("@vue/component")); diff --git a/lib/styles/context/vue-components/index.ts b/lib/styles/context/vue-components/index.ts index d7237c5..065f2eb 100644 --- a/lib/styles/context/vue-components/index.ts +++ b/lib/styles/context/vue-components/index.ts @@ -4,6 +4,7 @@ import findVueComponent from "./find-vue"; import type { RuleContext, ASTNode, AST } from "../../../types"; import type { Template } from "../../template"; import { isDefined } from "../../../utils/utils"; +import { getSourceCode } from "../../../utils/compat"; const traverseNodes = vueAST.traverseNodes; @@ -288,7 +289,7 @@ function getClassesOperatedByClassList( ): (AST.ESLintExpression | AST.ESLintSpreadElement)[] { const results: (AST.ESLintExpression | AST.ESLintSpreadElement)[] = []; traverseNodes(vueNode, { - visitorKeys: context.getSourceCode().visitorKeys, + visitorKeys: getSourceCode(context).visitorKeys, enterNode(node) { if ( node.type !== "CallExpression" || @@ -351,7 +352,7 @@ function getReturnStatements( | AST.ESLintFunctionDeclaration )[] = []; traverseNodes(body, { - visitorKeys: context.getSourceCode().visitorKeys, + visitorKeys: getSourceCode(context).visitorKeys, enterNode(node) { if (skipNodes.length) { return; diff --git a/lib/styles/parser/css-parser.ts b/lib/styles/parser/css-parser.ts index e0e4a42..4d85f39 100644 --- a/lib/styles/parser/css-parser.ts +++ b/lib/styles/parser/css-parser.ts @@ -485,7 +485,11 @@ function getESLintLineAndColumnFromPostCSSNode( offsetLocation, sourceLoc, ); - if (locName === "end") { + if ( + locName === "end" && + // The end location of the PostCSS root node has a different position than other nodes. + node.type !== "root" + ) { // End column is shifted by one. return { line, column: column + 1 }; } diff --git a/lib/styles/selectors/query/attribute-tracker.ts b/lib/styles/selectors/query/attribute-tracker.ts index dcd2d26..36a08e2 100644 --- a/lib/styles/selectors/query/attribute-tracker.ts +++ b/lib/styles/selectors/query/attribute-tracker.ts @@ -47,6 +47,7 @@ export function getAttributeValueNodes( // empty or syntax error continue; } + if (expression.type === "VGenericExpression") continue; const expressions = getReferenceExpressions(expression, context); if (!expressions) { // Expressions not found. diff --git a/lib/styles/selectors/query/index.ts b/lib/styles/selectors/query/index.ts index 79e3f4d..7b70e89 100644 --- a/lib/styles/selectors/query/index.ts +++ b/lib/styles/selectors/query/index.ts @@ -38,6 +38,7 @@ import { isVElement, isTransitionElement } from "../../../utils/templates"; import { isValidStyleContext } from "../../context/style"; import type { ReferenceExpressions } from "./reference-expression"; import { getReferenceExpressions } from "./reference-expression"; +import { getSourceCode } from "../../../utils/compat"; const TRANSITION_CLASS_BASES = [ "enter", @@ -130,7 +131,7 @@ class VueDocumentQueryContext extends QueryContext { public constructor(context: RuleContext, options: ParsedQueryOptions) { super(); - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const { ast } = sourceCode; this.elements = ast.templateBody ? [...genDescendantElements([ast.templateBody])] diff --git a/lib/styles/selectors/query/reference-expression.ts b/lib/styles/selectors/query/reference-expression.ts index aafb00a..41b83a2 100644 --- a/lib/styles/selectors/query/reference-expression.ts +++ b/lib/styles/selectors/query/reference-expression.ts @@ -1,5 +1,6 @@ import { getVueComponentContext } from "../../context"; import type { RuleContext, AST } from "../../../types"; +import { getSourceCode } from "../../../utils/compat"; export type ReferenceExpressions = | AST.ESLintExpression @@ -54,7 +55,7 @@ export function getReferenceExpressions( * Checks whether the given node within `