diff --git a/its/ruling/src/test/expected/jsts/file-for-rules/typescript-S1451.json b/its/ruling/src/test/expected/jsts/file-for-rules/typescript-S1451.json index 4ee803c6bad..2e88a5fb0b7 100644 --- a/its/ruling/src/test/expected/jsts/file-for-rules/typescript-S1451.json +++ b/its/ruling/src/test/expected/jsts/file-for-rules/typescript-S1451.json @@ -10,5 +10,8 @@ ], "file-for-rules:S6583.ts": [ 0 +], +"file-for-rules:S6849.tsx": [ +0 ] } diff --git a/its/ruling/src/test/expected/jsts/file-for-rules/typescript-S6849.json b/its/ruling/src/test/expected/jsts/file-for-rules/typescript-S6849.json new file mode 100644 index 00000000000..67cd5ca1550 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/file-for-rules/typescript-S6849.json @@ -0,0 +1,5 @@ +{ +"file-for-rules:S6849.tsx": [ +2 +] +} diff --git a/its/sources/jsts/custom/S6849.tsx b/its/sources/jsts/custom/S6849.tsx new file mode 100644 index 00000000000..b8f77e67407 --- /dev/null +++ b/its/sources/jsts/custom/S6849.tsx @@ -0,0 +1,3 @@ +function html() { + return ; +} diff --git a/packages/jsts/src/rules/S6849/cb.fixture.tsx b/packages/jsts/src/rules/S6849/cb.fixture.tsx new file mode 100644 index 00000000000..425e795ed3a --- /dev/null +++ b/packages/jsts/src/rules/S6849/cb.fixture.tsx @@ -0,0 +1,2 @@ +; // Noncompliant {{ elements must have the lang prop.}} +; // Noncompliant {{lang attribute must have a valid value.}} diff --git a/packages/jsts/src/rules/S6849/cb.test.ts b/packages/jsts/src/rules/S6849/cb.test.ts new file mode 100644 index 00000000000..8fe3ff0701c --- /dev/null +++ b/packages/jsts/src/rules/S6849/cb.test.ts @@ -0,0 +1,28 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 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 GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { check } from '../tools'; +import { rule } from './'; +import path from 'path'; + +const sonarId = path.basename(__dirname); + +describe(`Rule ${sonarId}`, () => { + check(sonarId, rule, __dirname); +}); diff --git a/packages/jsts/src/rules/S6849/index.ts b/packages/jsts/src/rules/S6849/index.ts new file mode 100644 index 00000000000..13438fe7e69 --- /dev/null +++ b/packages/jsts/src/rules/S6849/index.ts @@ -0,0 +1,20 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 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 GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +export { rule } from './rule'; diff --git a/packages/jsts/src/rules/S6849/rule.ts b/packages/jsts/src/rules/S6849/rule.ts new file mode 100644 index 00000000000..adfeeb80790 --- /dev/null +++ b/packages/jsts/src/rules/S6849/rule.ts @@ -0,0 +1,44 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 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 GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// https://sonarsource.github.io/rspec/#/rspec/S6849/javascript + +import { Rule } from 'eslint'; +import { rules as jsxA11yRules } from 'eslint-plugin-jsx-a11y'; +import { mergeRules } from '../helpers'; + +const langRule = jsxA11yRules['lang']; +const htmlHasLangRule = jsxA11yRules['html-has-lang']; + +export const rule: Rule.RuleModule = { + meta: { + hasSuggestions: true, + messages: { + ...langRule.meta!.messages, + ...htmlHasLangRule.meta!.messages, + }, + }, + + create(context: Rule.RuleContext) { + const langListener: Rule.RuleListener = langRule.create(context); + const htmlHasLangListener: Rule.RuleListener = htmlHasLangRule.create(context); + + return mergeRules(langListener, htmlHasLangListener); + }, +}; diff --git a/packages/jsts/src/rules/index.ts b/packages/jsts/src/rules/index.ts index 4ef2503de1c..96756c5eda6 100644 --- a/packages/jsts/src/rules/index.ts +++ b/packages/jsts/src/rules/index.ts @@ -101,6 +101,7 @@ import { rule as S1527 } from './S1527'; // future-reserved-words import { rule as S3531 } from './S3531'; // generator-without-yield import { rule as S4790 } from './S4790'; // hashing import { rule as S5691 } from './S5691'; // hidden-files +import { rule as S6849 } from './S6849'; // html-has-lang import { rule as S3785 } from './S3785'; // in-operator-type-error import { rule as S3686 } from './S3686'; // inconsistent-function-call import { rule as S2692 } from './S2692'; // index-of-compare-to-positive-number @@ -110,7 +111,6 @@ import { rule as S3415 } from './S3415'; // inverted-assertion-arguments import { rule as S6477 } from './S6477'; // jsx-key import { rule as S6481 } from './S6481'; // jsx-no-constructed-context-values import { rule as S6749 } from './S6749'; // jsx-no-useless-fragment -import { rule as S6788 } from './S6788'; // no-find-dom-node import { rule as S1439 } from './S1439'; // label-position import { rule as S5148 } from './S5148'; // link-with-target-blank import { rule as S4622 } from './S4622'; // max-union-size @@ -144,6 +144,7 @@ import { rule as S888 } from './S888'; // no-equals-in-for-termination import { rule as S6426 } from './S6426'; // no-exclusive-tests import { rule as S6643 } from './S6643'; // no-extend-native import { rule as S1116 } from './S1116'; // no-extra-semi +import { rule as S6788 } from './S6788'; // no-find-dom-node import { rule as S4139 } from './S4139'; // no-for-in-iterable import { rule as S1530 } from './S1530'; // no-function-declaration-in-block import { rule as S2990 } from './S2990'; // no-global-this @@ -381,6 +382,7 @@ rules['future-reserved-words'] = S1527; rules['generator-without-yield'] = S3531; rules['hashing'] = S4790; rules['hidden-files'] = S5691; +rules['html-has-lang'] = S6849; rules['in-operator-type-error'] = S3785; rules['inconsistent-function-call'] = S3686; rules['index-of-compare-to-positive-number'] = S2692; @@ -390,7 +392,6 @@ rules['inverted-assertion-arguments'] = S3415; rules['jsx-key'] = S6477; rules['jsx-no-constructed-context-values'] = S6481; rules['jsx-no-useless-fragment'] = S6749; -rules['no-find-dom-node'] = S6788; rules['label-position'] = S1439; rules['link-with-target-blank'] = S5148; rules['max-union-size'] = S4622; @@ -424,6 +425,7 @@ rules['no-equals-in-for-termination'] = S888; rules['no-exclusive-tests'] = S6426; rules['no-extend-native'] = S6643; rules['no-extra-semi'] = S1116; +rules['no-find-dom-node'] = S6788; rules['no-for-in-iterable'] = S4139; rules['no-function-declaration-in-block'] = S1530; rules['no-global-this'] = S2990; diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java index b7091620698..086964debd7 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java @@ -190,6 +190,7 @@ public static List> getAllChecks() { HashingCheck.class, HiddenFilesCheck.class, HookUseStateCheck.class, + HtmlHasLangCheck.class, IdenticalExpressionOnBinaryOperatorCheck.class, IdenticalFunctionsCheck.class, IgnoredReturnCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/HtmlHasLangCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/HtmlHasLangCheck.java new file mode 100644 index 00000000000..5ab4d4fa111 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/HtmlHasLangCheck.java @@ -0,0 +1,36 @@ +/** + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 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 GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.javascript.checks; + +import org.sonar.check.Rule; +import org.sonar.plugins.javascript.api.EslintBasedCheck; +import org.sonar.plugins.javascript.api.JavaScriptRule; +import org.sonar.plugins.javascript.api.TypeScriptRule; + +@TypeScriptRule +@JavaScriptRule +@Rule(key = "S6849") +public class HtmlHasLangCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "html-has-lang"; + } +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6849.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6849.html new file mode 100644 index 00000000000..29f922294f7 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6849.html @@ -0,0 +1,46 @@ +

The <a> tag in HTML is designed to create hyperlinks, which can link to different sections of the same page, different pages, or +even different websites. However, sometimes developers misuse <a> tags as buttons, which can lead to accessibility issues and +unexpected behavior.

+

This rule checks that <a> tags are used correctly as hyperlinks and not misused as buttons. It verifies that each +<a> tag has a href attribute, which is necessary for it to function as a hyperlink. If an <a> tag +is used without a href attribute, it behaves like a button, which is not its intended use.

+

Using the correct HTML elements for their intended purpose is crucial for accessibility and usability. It ensures that the website behaves as +expected and can be used by all users, including those using assistive technologies. Misusing HTML elements can lead to a poor user experience and +potential accessibility violations.

+

Compliance with this rule will ensure that your HTML code is semantically correct, accessible, and behaves as expected.

+

Why is this an issue?

+

Misusing <a> tags as buttons can lead to several issues:

+ +

How to fix it

+

To fix this issue, you should use the appropriate HTML elements for their intended purposes. If you need to create a hyperlink, use the +<a> tag with a href attribute. If you need to create a button, use the <button> tag.

+

Code examples

+

Noncompliant code example

+
+<a href="javascript:void(0)" onClick={foo}>Perform action</a>
+<a href="#" onClick={foo}>Perform action</a>
+<a onClick={foo}>Perform action</a>
+
+

Compliant solution

+
+<button onClick={foo}>Perform action</button>
+
+

Resources

+

Documentation

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6849.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6849.json new file mode 100644 index 00000000000..510b80e1276 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6849.json @@ -0,0 +1,28 @@ +{ + "title": "HTML elements should have a valid language attribute", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "accessibility", + "react" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-6849", + "sqKey": "S6849", + "scope": "All", + "quickfix": "targeted", + "code": { + "impacts": { + "RELIABILITY": "MEDIUM" + }, + "attribute": "LOGICAL" + }, + "compatibleLanguages": [ + "JAVASCRIPT", + "TYPESCRIPT" + ] +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json index 18ed5c4d42e..8d40ff487dc 100644 --- a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json @@ -313,6 +313,7 @@ "S6843", "S6844", "S6845", - "S6846" + "S6846", + "S6849" ] }