UNPKG

9.63 kBJavaScriptView Raw
1"use strict";
2var __extends = (this && this.__extends) || (function () {
3 var extendStatics = Object.setPrototypeOf ||
4 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
6 return function (d, b) {
7 extendStatics(d, b);
8 function __() { this.constructor = d; }
9 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
10 };
11})();
12Object.defineProperty(exports, "__esModule", { value: true });
13var ts = require("typescript");
14var Lint = require("tslint");
15var ErrorTolerantWalker_1 = require("./utils/ErrorTolerantWalker");
16var Utils_1 = require("./utils/Utils");
17var getImplicitRole_1 = require("./utils/getImplicitRole");
18var JsxAttribute_1 = require("./utils/JsxAttribute");
19var ROLE_STRING = 'role';
20exports.NO_HASH_FAILURE_STRING = 'Do not use # as anchor href.';
21exports.LINK_TEXT_TOO_SHORT_FAILURE_STRING = 'Link text or the alt text of image in link should be at least 4 characters long. ' +
22 'If you are not using <a> element as anchor, please specify explicit role, e.g. role=\'button\'';
23exports.UNIQUE_ALT_FAILURE_STRING = 'Links with images and text content, the alt attribute should be unique to the text content or empty.';
24exports.SAME_HREF_SAME_TEXT_FAILURE_STRING = 'Links with the same HREF should have the same link text.';
25exports.DIFFERENT_HREF_DIFFERENT_TEXT_FAILURE_STRING = 'Links that point to different HREFs should have different link text.';
26var Rule = (function (_super) {
27 __extends(Rule, _super);
28 function Rule() {
29 return _super !== null && _super.apply(this, arguments) || this;
30 }
31 Rule.prototype.apply = function (sourceFile) {
32 if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
33 var rule = new ReactA11yAnchorsRuleWalker(sourceFile, this.getOptions());
34 this.applyWithWalker(rule);
35 rule.validateAllAnchors();
36 return rule.getFailures();
37 }
38 return [];
39 };
40 Rule.metadata = {
41 ruleName: 'react-a11y-anchors',
42 type: 'functionality',
43 description: 'For accessibility of your website, anchor elements must have a href different from # and a text longer than 4.',
44 options: null,
45 optionsDescription: '',
46 typescriptOnly: true,
47 issueClass: 'Non-SDL',
48 issueType: 'Warning',
49 severity: 'Low',
50 level: 'Opportunity for Excellence',
51 group: 'Accessibility'
52 };
53 return Rule;
54}(Lint.Rules.AbstractRule));
55exports.Rule = Rule;
56var ReactA11yAnchorsRuleWalker = (function (_super) {
57 __extends(ReactA11yAnchorsRuleWalker, _super);
58 function ReactA11yAnchorsRuleWalker() {
59 var _this = _super !== null && _super.apply(this, arguments) || this;
60 _this.anchorInfoList = [];
61 return _this;
62 }
63 ReactA11yAnchorsRuleWalker.prototype.validateAllAnchors = function () {
64 var _this = this;
65 var sameHrefDifferentTexts = [];
66 var differentHrefSameText = [];
67 var _loop_1 = function () {
68 var current = this_1.anchorInfoList.shift();
69 this_1.anchorInfoList.forEach(function (anchorInfo) {
70 if (current.href &&
71 current.href === anchorInfo.href &&
72 (current.text !== anchorInfo.text || current.altText !== anchorInfo.altText) &&
73 !Utils_1.Utils.contains(sameHrefDifferentTexts, anchorInfo)) {
74 sameHrefDifferentTexts.push(anchorInfo);
75 _this.addFailureAt(anchorInfo.start, anchorInfo.width, exports.SAME_HREF_SAME_TEXT_FAILURE_STRING + _this.firstPosition(current));
76 }
77 if (current.href !== anchorInfo.href &&
78 current.text === anchorInfo.text &&
79 current.altText === anchorInfo.altText &&
80 !Utils_1.Utils.contains(differentHrefSameText, anchorInfo)) {
81 differentHrefSameText.push(anchorInfo);
82 _this.addFailureAt(anchorInfo.start, anchorInfo.width, exports.DIFFERENT_HREF_DIFFERENT_TEXT_FAILURE_STRING + _this.firstPosition(current));
83 }
84 });
85 };
86 var this_1 = this;
87 while (this.anchorInfoList.length > 0) {
88 _loop_1();
89 }
90 };
91 ReactA11yAnchorsRuleWalker.prototype.firstPosition = function (anchorInfo) {
92 var startPosition = this.createFailure(anchorInfo.start, anchorInfo.width, '').getStartPosition().getLineAndCharacter();
93 var character = startPosition.character + 1;
94 var line = startPosition.line + 1;
95 return " First link at character: " + character + " line: " + line;
96 };
97 ReactA11yAnchorsRuleWalker.prototype.visitJsxSelfClosingElement = function (node) {
98 this.validateAnchor(node, node);
99 _super.prototype.visitJsxSelfClosingElement.call(this, node);
100 };
101 ReactA11yAnchorsRuleWalker.prototype.visitJsxElement = function (node) {
102 this.validateAnchor(node, node.openingElement);
103 _super.prototype.visitJsxElement.call(this, node);
104 };
105 ReactA11yAnchorsRuleWalker.prototype.validateAnchor = function (parent, openingElement) {
106 if (openingElement.tagName.getText() === 'a') {
107 var anchorInfo = {
108 href: this.getAttribute(openingElement, 'href'),
109 text: this.anchorText(parent),
110 altText: this.imageAlt(parent),
111 start: parent.getStart(),
112 width: parent.getWidth()
113 };
114 if (anchorInfo.href === '#') {
115 this.addFailureAt(anchorInfo.start, anchorInfo.width, exports.NO_HASH_FAILURE_STRING);
116 }
117 if (anchorInfo.altText && anchorInfo.altText === anchorInfo.text) {
118 this.addFailureAt(anchorInfo.start, anchorInfo.width, exports.UNIQUE_ALT_FAILURE_STRING);
119 }
120 var anchorInfoTextLength = anchorInfo.text ? anchorInfo.text.length : 0;
121 var anchorImageAltTextLength = anchorInfo.altText ? anchorInfo.altText.length : 0;
122 if (this.anchorRole(openingElement) === 'link' &&
123 anchorInfoTextLength < 4 &&
124 anchorImageAltTextLength < 4) {
125 this.addFailureAt(anchorInfo.start, anchorInfo.width, exports.LINK_TEXT_TOO_SHORT_FAILURE_STRING);
126 }
127 this.anchorInfoList.push(anchorInfo);
128 }
129 };
130 ReactA11yAnchorsRuleWalker.prototype.getAttribute = function (openingElement, attributeName) {
131 var attributes = JsxAttribute_1.getJsxAttributesFromJsxElement(openingElement);
132 var attribute = attributes[attributeName];
133 return attribute ? JsxAttribute_1.getStringLiteral(attribute) : '';
134 };
135 ReactA11yAnchorsRuleWalker.prototype.anchorText = function (root) {
136 var _this = this;
137 var title = '';
138 if (root.kind === ts.SyntaxKind.JsxElement) {
139 var jsxElement = root;
140 jsxElement.children.forEach(function (child) {
141 title += _this.anchorText(child);
142 });
143 }
144 else if (root.kind === ts.SyntaxKind.JsxText) {
145 var jsxText = root;
146 title += jsxText.getText();
147 }
148 else if (root.kind === ts.SyntaxKind.StringLiteral) {
149 var literal = root;
150 title += literal.text;
151 }
152 else if (root.kind === ts.SyntaxKind.JsxExpression) {
153 var expression = root;
154 title += this.anchorText(expression.expression);
155 }
156 else if (root.kind !== ts.SyntaxKind.JsxSelfClosingElement) {
157 title += '<unknown>';
158 }
159 return title;
160 };
161 ReactA11yAnchorsRuleWalker.prototype.anchorRole = function (root) {
162 var attributesInElement = JsxAttribute_1.getJsxAttributesFromJsxElement(root);
163 var roleProp = attributesInElement[ROLE_STRING];
164 return roleProp ? JsxAttribute_1.getStringLiteral(roleProp) : getImplicitRole_1.getImplicitRole(root);
165 };
166 ReactA11yAnchorsRuleWalker.prototype.imageAltAttribute = function (openingElement) {
167 if (openingElement.tagName.getText() === 'img') {
168 var altAttribute = this.getAttribute(openingElement, 'alt');
169 return altAttribute === undefined ? '<unknown>' : altAttribute;
170 }
171 return '';
172 };
173 ReactA11yAnchorsRuleWalker.prototype.imageAlt = function (root) {
174 var _this = this;
175 var altText = '';
176 if (root.kind === ts.SyntaxKind.JsxElement) {
177 var jsxElement = root;
178 altText += this.imageAltAttribute(jsxElement.openingElement);
179 jsxElement.children.forEach(function (child) {
180 altText += _this.imageAlt(child);
181 });
182 }
183 if (root.kind === ts.SyntaxKind.JsxSelfClosingElement) {
184 var jsxSelfClosingElement = root;
185 altText += this.imageAltAttribute(jsxSelfClosingElement);
186 }
187 return altText;
188 };
189 return ReactA11yAnchorsRuleWalker;
190}(ErrorTolerantWalker_1.ErrorTolerantWalker));
191var AnchorInfo = (function () {
192 function AnchorInfo() {
193 this.href = '';
194 this.text = '';
195 this.altText = '';
196 this.start = 0;
197 this.width = 0;
198 }
199 return AnchorInfo;
200}());
201//# sourceMappingURL=reactA11yAnchorsRule.js.map
\No newline at end of file