From d17814c862fd29d0f2e53e0d6f8439c00d6ad6d2 Mon Sep 17 00:00:00 2001 From: Michal Zgliczynski Date: Wed, 20 Aug 2025 13:19:23 +0200 Subject: [PATCH] wip adding angular-template --- package-lock.json | 52 +++++++++++++++++++ package.json | 1 + packages/jsts/src/rules/S1082/meta.ts | 1 + packages/jsts/src/rules/S1082/rule.ts | 10 +++- .../src/rules/external/angular-template.ts | 36 +++++++++++++ .../{typescript-eslint => }/sanitize.ts | 8 +-- .../rules/external/typescript-eslint/index.ts | 2 +- packages/jsts/tests/rules/index.test.ts | 3 ++ tools/generate-external-rules-docs.ts | 2 + 9 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 packages/jsts/src/rules/external/angular-template.ts rename packages/jsts/src/rules/external/{typescript-eslint => }/sanitize.ts (84%) diff --git a/package-lock.json b/package-lock.json index 26bc5af5008..46be472d17c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "license": "LGPL-3.0-only", "dependencies": { "@angular-eslint/eslint-plugin": "20.1.1", + "@angular-eslint/eslint-plugin-template": "20.1.1", "@babel/core": "7.28.3", "@babel/eslint-parser": "7.28.0", "@babel/plugin-proposal-decorators": "7.28.0", @@ -137,6 +138,57 @@ "typescript": "*" } }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "20.1.1", + "resolved": "https://repox.jfrog.io/artifactory/api/npm/npm/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-20.1.1.tgz", + "integrity": "sha512-dRqfxYvgOC4DZqvRTmxoIUMeIqTzcIkRcMVEuP8qvR10KHAWDkV7xT4f7BAee9deI/lzoAk3tk5wkQg6POQo7Q==", + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.1.1", + "@angular-eslint/utils": "20.1.1", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@angular-eslint/template-parser": "20.1.1", + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "20.1.1", + "resolved": "https://repox.jfrog.io/artifactory/api/npm/npm/@angular-eslint/template-parser/-/template-parser-20.1.1.tgz", + "integrity": "sha512-giIMYORf8P8MbBxh6EUfiR/7Y+omxJtK2C7a8lYTtLSOIGO0D8c8hXx9hTlPcdupVX+xZXDuZ85c9JDen+JSSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.1.1", + "eslint-scope": "^8.0.2" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://repox.jfrog.io/artifactory/api/npm/npm/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@angular-eslint/utils": { "version": "20.1.1", "resolved": "https://repox.jfrog.io/artifactory/api/npm/npm/@angular-eslint/utils/-/utils-20.1.1.tgz", diff --git a/package.json b/package.json index 44da142f6aa..c9c991bb141 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "dependencies": { "@babel/core": "7.28.3", "@angular-eslint/eslint-plugin": "20.1.1", + "@angular-eslint/eslint-plugin-template": "20.1.1", "@babel/eslint-parser": "7.28.0", "@babel/plugin-proposal-decorators": "7.28.0", "@babel/preset-env": "7.28.3", diff --git a/packages/jsts/src/rules/S1082/meta.ts b/packages/jsts/src/rules/S1082/meta.ts index 8a40e56c92b..8f8e1995f59 100644 --- a/packages/jsts/src/rules/S1082/meta.ts +++ b/packages/jsts/src/rules/S1082/meta.ts @@ -19,4 +19,5 @@ export const eslintId = 'mouse-events-a11y'; export const externalRules = [ { externalPlugin: 'jsx-a11y', externalRule: 'mouse-events-have-key-events' }, { externalPlugin: 'jsx-a11y', externalRule: 'click-events-have-key-events' }, + { externalPlugin: '@angular-eslint-template', externalRule: 'click-events-have-key-events' }, ]; diff --git a/packages/jsts/src/rules/S1082/rule.ts b/packages/jsts/src/rules/S1082/rule.ts index bb6c5cfc8a0..01106969d93 100644 --- a/packages/jsts/src/rules/S1082/rule.ts +++ b/packages/jsts/src/rules/S1082/rule.ts @@ -20,9 +20,11 @@ import type { Rule } from 'eslint'; import { generateMeta, mergeRules } from '../helpers/index.js'; import { rules } from '../external/a11y.js'; import * as meta from './generated-meta.js'; +import { rules as angularTemplateRules } from '../external/angular-template.js'; const mouseEventsHaveKeyEvents = rules['mouse-events-have-key-events']; const clickEventsHaveKeyEvents = rules['click-events-have-key-events']; +const angularClickEventsHaveKeyEvents = angularTemplateRules['click-events-have-key-events']; export const rule: Rule.RuleModule = { meta: generateMeta(meta, { @@ -30,12 +32,18 @@ export const rule: Rule.RuleModule = { messages: { ...mouseEventsHaveKeyEvents.meta!.messages, ...clickEventsHaveKeyEvents.meta!.messages, + ...angularClickEventsHaveKeyEvents.meta!.messages, }, }), create(context: Rule.RuleContext) { const mouseEventsHaveKeyEventsListener = mouseEventsHaveKeyEvents.create(context); const clickEventsHaveKeyEventsListener = clickEventsHaveKeyEvents.create(context); + const angularClickEventsHaveKeyEventsListener = angularClickEventsHaveKeyEvents.create(context); - return mergeRules(mouseEventsHaveKeyEventsListener, clickEventsHaveKeyEventsListener); + return mergeRules( + mouseEventsHaveKeyEventsListener, + clickEventsHaveKeyEventsListener, + angularClickEventsHaveKeyEventsListener, + ); }, }; diff --git a/packages/jsts/src/rules/external/angular-template.ts b/packages/jsts/src/rules/external/angular-template.ts new file mode 100644 index 00000000000..d843f3c71cc --- /dev/null +++ b/packages/jsts/src/rules/external/angular-template.ts @@ -0,0 +1,36 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +import type { Rule } from 'eslint'; +import pkg from '@angular-eslint/eslint-plugin-template'; +const { rules: angularTemplateRules } = pkg; +import { sanitize } from './sanitize.js'; + +/** + * Angular Template ESLint rules that rely on type information fail at runtime because + * they unconditionally assume that TypeScript's type checker is available. + */ +const sanitized: Record = {}; +for (const ruleKey of Object.keys(angularTemplateRules) as Array< + keyof typeof angularTemplateRules +>) { + sanitized[ruleKey] = sanitize(angularTemplateRules[ruleKey]! as unknown as Rule.RuleModule); +} + +/** + * Angular Template ESLint rules. + */ +export const rules = sanitized; diff --git a/packages/jsts/src/rules/external/typescript-eslint/sanitize.ts b/packages/jsts/src/rules/external/sanitize.ts similarity index 84% rename from packages/jsts/src/rules/external/typescript-eslint/sanitize.ts rename to packages/jsts/src/rules/external/sanitize.ts index a0a5c98b709..fddfdfb09cc 100644 --- a/packages/jsts/src/rules/external/typescript-eslint/sanitize.ts +++ b/packages/jsts/src/rules/external/sanitize.ts @@ -15,16 +15,16 @@ * along with this program; if not, see https://sonarsource.com/license/ssal/ */ import type { Rule } from 'eslint'; -import { isRequiredParserServices } from '../../helpers/index.js'; +import { isRequiredParserServices } from '../helpers/index.js'; /** * Sanitizes a TypeScript ESLint rule * - * TypeScript ESLint rules that relies on TypeScript's type system unconditionally assumes - * that the type checker is always available. Linting a source code with such rules could + * TypeScript ESLint rules that rely on TypeScript's type system unconditionally assume + * that the type checker is always available. Linting source code with such rules could * lead to a runtime error if that assumption turned out to be wrong for whatever reason. * - * Aa TypeScript ESLint rule needs, therefore, to be sanitized in case its implementation + * A TypeScript ESLint rule needs, therefore, to be sanitized in case its implementation * relies on type checking. The metadata of such a rule sets the `requiresTypeChecking` * property to `true`. * diff --git a/packages/jsts/src/rules/external/typescript-eslint/index.ts b/packages/jsts/src/rules/external/typescript-eslint/index.ts index 410e22d438c..3a246d52493 100644 --- a/packages/jsts/src/rules/external/typescript-eslint/index.ts +++ b/packages/jsts/src/rules/external/typescript-eslint/index.ts @@ -17,7 +17,7 @@ import type { Rule } from 'eslint'; import pkg from '@typescript-eslint/eslint-plugin'; const { rules: tsEslintRules } = pkg; -import { sanitize } from './sanitize.js'; +import { sanitize } from '../sanitize.js'; /** * TypeScript ESLint rules that rely on type information fail at runtime because diff --git a/packages/jsts/tests/rules/index.test.ts b/packages/jsts/tests/rules/index.test.ts index 6bdb2f471d0..e98ee54419f 100644 --- a/packages/jsts/tests/rules/index.test.ts +++ b/packages/jsts/tests/rules/index.test.ts @@ -29,7 +29,9 @@ import { rules as tsEslintRules } from '../../src/rules/external/typescript-esli import { rules as importRules } from 'eslint-plugin-import'; import { rules as reactHooksRules } from 'eslint-plugin-react-hooks'; import angularPlugin from '@angular-eslint/eslint-plugin'; +import angularTemplatePlugin from '@angular-eslint/eslint-plugin-template'; const { rules: angularRules } = angularPlugin; +const { rules: angularTemplateRules } = angularTemplatePlugin; const allExternalRules = { eslint: key => getESLintCoreRule(key), @@ -41,6 +43,7 @@ const allExternalRules = { '@stylistic/eslint-plugin-ts': async key => await import(`@stylistic/eslint-plugin-ts/rules/${key}`), '@angular-eslint': key => angularRules[key], + '@angular-eslint-template': key => angularTemplateRules[key], }; const externalPlugins = Object.keys(allExternalRules); diff --git a/tools/generate-external-rules-docs.ts b/tools/generate-external-rules-docs.ts index c84edc46cfd..c79cf83798e 100644 --- a/tools/generate-external-rules-docs.ts +++ b/tools/generate-external-rules-docs.ts @@ -90,6 +90,8 @@ function externalURL(plugin: string, key: string) { return `https://github.com/eslint-stylistic/eslint-stylistic/blob/main/packages/eslint-plugin/rules/${key}/README.md`; case '@angular-eslint': return `https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/${key}.md`; + case '@angular-eslint-template': + return `https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/${key}.md`; default: throw new Error(`Error generating URL for unknown ESLint plugin ${plugin}`); }