Skip to content

Commit a2ddba3

Browse files
authored
feat: deprecate the use doT.js for messages (#1938)
* feat: dont use doT.js for messages * fix test * const * conditional dot based on message * fix test * test * more fixes * revert ja * use messageKey to determine object message * add docs
1 parent c631bc1 commit a2ddba3

47 files changed

Lines changed: 812 additions & 98 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Axe can be built using your local language. To do so, a localization file must b
9393

9494
This will create a new build for axe, called `axe.<lang>.js` and `axe.<lang>.min.js`. If you want to build localized versions, simply pass in `--all-lang` instead.
9595

96-
To create a new translation for axe, start by running `grunt translate --lang=<langcode>`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below.
96+
To create a new translation for axe, start by running `grunt translate --lang=<langcode>`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/docs/check-message-template.md).
9797

9898
To update existing translation file, re-run `grunt translate --lang=<langcode>`. This will add new messages used in English and remove messages which were not used in English.
9999

@@ -116,7 +116,7 @@ axe.configure({
116116
'aria-errormessage': {
117117
// Note: doT (https://github.com/olado/dot) templates are supported here.
118118
fail:
119-
'Der Wert der aria-errormessage {{~it.data:value}} `{{=value}}{{~}}` muss eine Technik verwenden, um die Message anzukündigen (z. B., aria-live, aria-describedby, role=alert, etc.).'
119+
'Der Wert der aria-errormessage ${data.values}` muss eine Technik verwenden, um die Message anzukündigen (z. B., aria-live, aria-describedby, role=alert, etc.).'
120120
}
121121
// ...
122122
}

build/configure.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var dot = require('@deque/dot');
77
var templates = require('./templates');
88
var buildManual = require('./build-manual');
99
var entities = new (require('html-entities')).AllHtmlEntities();
10+
var dotRegex = /\{\{.+?\}\}/g;
1011

1112
var descriptionHeaders =
1213
'| Rule ID | Description | Impact | Tags | Enabled by default | Failures | Needs Review |\n| :------- | :------- | :------- | :------- | :------- | :------- | :------- |\n';
@@ -59,15 +60,18 @@ function buildRules(grunt, options, commons, callback) {
5960
Object.keys(result.messages).forEach(function(key) {
6061
// only convert to templated function for strings
6162
// objects handled later in publish-metadata.js
62-
if (typeof result.messages[key] !== 'object') {
63+
if (
64+
typeof result.messages[key] !== 'object' &&
65+
dotRegex.test(result.messages[key])
66+
) {
6367
result.messages[key] = dot
6468
.template(result.messages[key])
6569
.toString();
6670
}
6771
});
6872
}
6973
//TODO this is actually failureSummaries, property name should better reflect that
70-
if (result.failureMessage) {
74+
if (result.failureMessage && dotRegex.test(result.failureMessage)) {
7175
result.failureMessage = dot.template(result.failureMessage).toString();
7276
}
7377
return result;
@@ -86,7 +90,10 @@ function buildRules(grunt, options, commons, callback) {
8690
function getIncompleteMsg(summaries) {
8791
var result = {};
8892
summaries.forEach(function(summary) {
89-
if (summary.incompleteFallbackMessage) {
93+
if (
94+
summary.incompleteFallbackMessage &&
95+
dotRegex.test(summary.incompleteFallbackMessage)
96+
) {
9097
result = dot.template(summary.incompleteFallbackMessage).toString();
9198
}
9299
});

build/tasks/validate.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ function hasMultipleOutcomes(messages) {
3737
switch (key) {
3838
case 'pass':
3939
case 'fail':
40-
return typeof messages[key] === 'string';
41-
4240
case 'incomplete':
4341
return ['string', 'object'].includes(typeof messages[key]);
4442

doc/check-message-template.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Check Message Template
2+
3+
Axe-core uses a custom template to handle dynamic check messages (messages that use the `data` property to output values or to determine which message to display). The structure for the messages is as follows:
4+
5+
## Simple Message
6+
7+
A simple message is just a string that doesn't use the `data` property. Most checks uses this format.
8+
9+
```json
10+
{
11+
"messages": {
12+
"pass": "Simple message for a passing check"
13+
}
14+
}
15+
```
16+
17+
## Message with Data
18+
19+
A message can also use the `data` property to output information from the check. If `data` is a String, Boolean, or Number, you can use the syntax `${data}` to have the message output the value of the `data` property.
20+
21+
```js
22+
// check.js
23+
this.data(10);
24+
25+
// check.json
26+
{
27+
"messages": {
28+
"pass": "Passed with a value of ${data}"
29+
// => "Passed with a value of 10"
30+
}
31+
}
32+
```
33+
34+
If `data` is an object, you can access properties of the object using the syntax `${data.propName}`.
35+
36+
```js
37+
// check.js
38+
this.data({
39+
contrast: '3:1',
40+
fontSize: '12px'
41+
});
42+
43+
// check.json
44+
{
45+
"messages": {
46+
"fail": "Color-contrast failed with a contrast of ${data.contrast} and font size of ${data.fontSize}"
47+
// => "Color-contrast failed with a contrast of 3:1 and font size of 12px"
48+
}
49+
}
50+
```
51+
52+
## Singular and Plural Messages
53+
54+
If the message needs to to know how many items are in the `data` property to determine the type of language to use (singular or plural), you can structure the message to use `singular` and `plural` properties. Use the syntax `${data.values}` to have the message output a comma-separated list of the items (`data.values` is provided by the template code for you).
55+
56+
```js
57+
// check.js
58+
this.data(['item1', 'item2']);
59+
60+
// check.json
61+
{
62+
"messages": {
63+
"fail": {
64+
"singular": "Attribute ${data.values} is not allowed",
65+
"plural": "Attributes: ${data.values} are not allowed"
66+
}
67+
// => Attributes: item1, item2 are not allowed
68+
}
69+
}
70+
```
71+
72+
## Message Determined by Data
73+
74+
Lastly, a message can use the `data` property to determine which message to display. Structure the message to use properties whose keys are the possible values of `data.messageKey`. You should also provide a `default` message that will be displayed if `messageKey` is not set.
75+
76+
```js
77+
// check.js
78+
this.data({
79+
messageKey: 'imgNode'
80+
});
81+
82+
// check.json
83+
{
84+
"messages": {
85+
"incomplete": {
86+
"default": "Color-contrast could not be determined"
87+
"bgImage": "Element's background color could not be determined due to a background image",
88+
"imgNode": "Element's background color could not be determined because element contains an image node"
89+
}
90+
// => Element's background color could not be determined because element contains an image node
91+
}
92+
}
93+
```
94+
95+
The messages can still use the syntax `${data.propName}` to access other properties on the `data` property.
96+
97+
## Migrating From doT.js Template in Translations
98+
99+
Axe-core use to use doT.js for it's temple library. To migrate from doT.js in a translation file, do the following:
100+
101+
- If the message used `{{=it.data}}` or `{{=it.data.propName}}`, change the message to use the syntax `${data}` or `${data.propName}`.
102+
103+
```diff
104+
{
105+
"messages": {
106+
- "incomplete": "Check that the <label> does not need be part of the ARIA {{=it.data}} field's name"
107+
+ "incomplete": "Check that the <label> does not need be part of the ARIA ${data} field's name"
108+
}
109+
}
110+
```
111+
112+
- If the message used `{{=it.data && it.data.length` to determine using singular or plural language, change the message structure of the message to instead use the `singular` and `plural` properties. Replace `{{=it.data.join(', ')}}` with `${data.values}`.
113+
114+
```diff
115+
{
116+
"messages": {
117+
- "fail": "Attribute{{=it.data && it.data.length > 1 ? 's' : ''}} {{=it.data.join(', ')}} {{=it.data && it.data.length > 1 ? 'are' : ' is'}} not allowed
118+
+ "fail": {
119+
+ "singular": "Attribute ${data.values} is not allowed",
120+
+ "plural": "Attributes: ${data.values} are not allowed"
121+
+ }
122+
}
123+
}
124+
```

lib/checks/aria/allowed-attr.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"impact": "critical",
66
"messages": {
77
"pass": "ARIA attributes are used correctly for the defined role",
8-
"fail": "ARIA attribute{{=it.data && it.data.length > 1 ? 's are' : ' is'}} not allowed:{{~it.data:value}} {{=value}}{{~}}"
8+
"fail": {
9+
"singular": "ARIA attribute is not allowed: ${data.values}",
10+
"plural": "ARIA attributes are not allowed: ${data.values}"
11+
}
912
}
1013
}
1114
}

lib/checks/aria/aria-allowed-role.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@
99
"impact": "minor",
1010
"messages": {
1111
"pass": "ARIA role is allowed for given element",
12-
"fail": "ARIA role{{=it.data && it.data.length > 1 ? 's' : ''}} {{=it.data.join(', ')}} {{=it.data && it.data.length > 1 ? 'are' : ' is'}} not allowed for given element",
13-
"incomplete": "ARIA role{{=it.data && it.data.length > 1 ? 's' : ''}} {{=it.data.join(', ')}} must be removed when the element is made visible, as {{=it.data && it.data.length > 1 ? 'they are' : 'it is'}} not allowed for the element"
12+
"fail": {
13+
"singular": "ARIA role ${data.values} is not allowed for given element",
14+
"plural": "ARIA roles ${data.values} are not allowed for given element"
15+
},
16+
"incomplete": {
17+
"singular": "ARIA role ${data.values} must be removed when the element is made visible, as it is not allowed for the element",
18+
"plural": "ARIA roles ${data.values} must be removed when the element is made visible, as they are not allowed for the element"
19+
}
1420
}
1521
}
1622
}

lib/checks/aria/errormessage.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"impact": "critical",
66
"messages": {
77
"pass": "Uses a supported aria-errormessage technique",
8-
"fail": "aria-errormessage value{{=it.data && it.data.length > 1 ? 's' : ''}} {{~it.data:value}} `{{=value}}{{~}}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)"
8+
"fail": {
9+
"singular": "aria-errormessage value `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)",
10+
"plural": "aria-errormessage values `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)"
11+
}
912
}
1013
}
1114
}

lib/checks/aria/no-implicit-explicit-label.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"impact": "moderate",
66
"messages": {
77
"pass": "There is no mismatch between a <label> and accessible name",
8-
"incomplete": "Check that the <label> does not need be part of the ARIA {{=it.data}} field's name"
8+
"incomplete": "Check that the <label> does not need be part of the ARIA ${data} field's name"
99
}
1010
}
1111
}

lib/checks/aria/required-attr.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"impact": "critical",
66
"messages": {
77
"pass": "All required ARIA attributes are present",
8-
"fail": "Required ARIA attribute{{=it.data && it.data.length > 1 ? 's' : ''}} not present:{{~it.data:value}} {{=value}}{{~}}"
8+
"fail": {
9+
"singular": "Required ARIA attribute not present: ${data.values}",
10+
"plural": "Required ARIA attributes not present: ${data.values}"
11+
}
912
}
1013
}
1114
}

lib/checks/aria/required-children.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@
1919
"impact": "critical",
2020
"messages": {
2121
"pass": "Required ARIA children are present",
22-
"fail": "Required ARIA {{=it.data && it.data.length > 1 ? 'children' : 'child'}} role not present:{{~it.data:value}} {{=value}}{{~}}",
23-
"incomplete": "Expecting ARIA {{=it.data && it.data.length > 1 ? 'children' : 'child'}} role to be added:{{~it.data:value}} {{=value}}{{~}}"
22+
"fail": {
23+
"singular": "Required ARIA child role not present: ${data.values}",
24+
"plural": "Required ARIA children role not present: ${data.values}"
25+
},
26+
"incomplete": {
27+
"singular": "Expecting ARIA child role to be added: ${data.values}",
28+
"plural": "Expecting ARIA children role to be added: ${data.values}"
29+
}
2430
}
2531
}
2632
}

0 commit comments

Comments
 (0)