diff --git a/.cspell.json b/.cspell.json index b442771..0ff2fff 100644 --- a/.cspell.json +++ b/.cspell.json @@ -20,6 +20,7 @@ "jerone", "lcov", "postpack", + "rimraf", "SARIF", "Snyk", "sonarcloud", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f4b32e..18f0ff0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 - run: npm ci --no-fund - run: npm run lint @@ -45,18 +45,18 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: "npm" # Cannot use `npm ci`, because this package uses ESLint plugins, # which conflict with the different installations below. - # Also the package-lock file is to new for npm on older NodeJS versions. + # Also the package-lock file is too new for npm on older NodeJS versions. - name: Install packages - run: npm install rimraf typescript jest jest-junit ts-jest + run: npm install rimraf typescript jest jest-junit ts-jest semver @types/semver - name: Install Angular ESLint ${{ matrix.angular-eslint-version[0] }} run: | - npm install \ + npm install --legacy-peer-deps \ @angular-eslint/template-parser@${{ matrix.angular-eslint-version[0] }} \ @angular-eslint/utils@${{ matrix.angular-eslint-version[0] }} \ @angular/compiler@${{ matrix.angular-eslint-version[0] }} \ diff --git a/package-lock.json b/package-lock.json index 8ddf74a..9b29384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "jest-junit": "16.0.0", "lockfile-lint": "4.12.1", "rimraf": "5.0.1", - "semver": "7.5.4", + "semver": "7.6.0", "ts-jest": "29.1.1", "typescript": "5.1.6" }, @@ -6192,9 +6192,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" diff --git a/package.json b/package.json index 2b54183..2dd7c06 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "jest-junit": "16.0.0", "lockfile-lint": "4.12.1", "rimraf": "5.0.1", - "semver": "7.5.4", + "semver": "7.6.0", "ts-jest": "29.1.1", "typescript": "5.1.6" }, diff --git a/src/lib/external/ensure-template-parser.ts b/src/lib/external/ensure-template-parser.ts new file mode 100644 index 0000000..8f7fc95 --- /dev/null +++ b/src/lib/external/ensure-template-parser.ts @@ -0,0 +1,64 @@ +// The method `ensureTemplateParser` is exported since ESLint Angular v14.4.0. +// Any version before that requires the fallback. +// See https://github.com/angular-eslint/angular-eslint/issues/888 +// Copied from https://github.com/angular-eslint/angular-eslint/blob/025ad9df4006ccd482b85df93ef52a0d5ebfa29d/packages/utils/src/eslint-plugin-template/parser-services.ts#L28-L45 +// Use `import { ensureTemplateParser } from "@angular-eslint/utils";` once ESLint Angular < v14.4.0 is dropped. + +import type { + ParseSourceSpan, + TmplAstElement, +} from "@angular-eslint/bundled-angular-compiler"; +import type { TSESLint, TSESTree } from "@typescript-eslint/utils"; + +export interface TemplateParserServices { + convertNodeSourceSpanToLoc: ( + sourceSpan: ParseSourceSpan, + ) => TSESTree.SourceLocation; + convertElementSourceSpanToLoc: ( + context: Readonly>>, + node: TmplAstElement, + ) => TSESTree.SourceLocation; +} + +/** + * Utility for rule authors to ensure that their rule is correctly being used with @angular-eslint/template-parser + * If @angular-eslint/template-parser is not the configured parser when the function is invoked it will throw + */ +export function ensureTemplateParser( + context: Readonly>>, +): void { + try { + import("@angular-eslint/utils") + .then(({ default: utils }) => { + try { + utils.ensureTemplateParser(context); + } catch { + ensureTemplateParserFallback(context); + } + }) + .catch(() => { + ensureTemplateParserFallback(context); + }); + } catch { + ensureTemplateParserFallback(context); + } +} + +function ensureTemplateParserFallback( + context: Readonly>>, +): void { + if ( + !(context.parserServices as unknown as TemplateParserServices) + ?.convertNodeSourceSpanToLoc || + !(context.parserServices as unknown as TemplateParserServices) + ?.convertElementSourceSpanToLoc + ) { + /** + * The user needs to have configured "parser" in their eslint config and set it + * to @angular-eslint/template-parser + */ + throw new Error( + "You have used a rule which requires '@angular-eslint/template-parser' to be used as the 'parser' in your ESLint config.", + ); + } +} diff --git a/src/lib/rules/eslint-plugin-angular-template-consistent-this.ts b/src/lib/rules/eslint-plugin-angular-template-consistent-this.ts index 67cd9da..41872b4 100644 --- a/src/lib/rules/eslint-plugin-angular-template-consistent-this.ts +++ b/src/lib/rules/eslint-plugin-angular-template-consistent-this.ts @@ -5,11 +5,11 @@ import type { TmplAstVariable, PropertyRead, } from "@angular-eslint/bundled-angular-compiler"; -import { ensureTemplateParser } from "@angular-eslint/utils"; import type { TSESLint, TSESTree } from "@typescript-eslint/utils"; import { MESSAGE_IDS } from "../message-ids"; import type { MessageIds, AstWithParent, RuleOptions } from "../types"; import Utils from "../utils"; +import { ensureTemplateParser } from "../external/ensure-template-parser"; export const RULE_NAME = "eslint-plugin-angular-template-consistent-this"; diff --git a/tsconfig.json b/tsconfig.json index d6038ea..a37ef9f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,5 @@ "compilerOptions": { "sourceMap": true }, - "include": ["src", "tests"] + "include": ["src", "tests", "./typings.d.ts"] } diff --git a/typings.d.ts b/typings.d.ts new file mode 100644 index 0000000..8a82314 --- /dev/null +++ b/typings.d.ts @@ -0,0 +1,8 @@ +import type { TSESLint } from "@typescript-eslint/utils"; + +// See explanation in `./src/lib/external/ensure-template-parser.ts` why this typing is needed. +declare module "@angular-eslint/utils" { + export function ensureTemplateParser( + context: Readonly>>, + ): void; +}