testilo 21.2.4 → 21.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "21.2.4",
3
+ "version": "21.4.0",
4
4
  "description": "Prepares and processes Testaro reports",
5
5
  "main": "aim.js",
6
6
  "scripts": {
@@ -2736,6 +2736,21 @@ exports.issues = {
2736
2736
  }
2737
2737
  }
2738
2738
  },
2739
+ itemIDBad: {
2740
+ summary: 'itemid invalid',
2741
+ why: 'User cannot get help to identify a referent',
2742
+ wcag: '1.3.1',
2743
+ weight: 4,
2744
+ tools: {
2745
+ nuVal: {
2746
+ 'The itemid attribute must not be specified on elements that do not have both an itemscope attribute and an itemtype attribute specified.': {
2747
+ variable: false,
2748
+ quality: 1,
2749
+ what: 'Element has an itemid attribute without both an itemscope and an itemtype attribute'
2750
+ }
2751
+ }
2752
+ }
2753
+ },
2739
2754
  itemTypeBad: {
2740
2755
  summary: 'itemtype invalid',
2741
2756
  why: 'User cannot get help on the definition of a term',
@@ -2898,6 +2913,11 @@ exports.issues = {
2898
2913
  variable: false,
2899
2914
  quality: 1,
2900
2915
  what: 'Table structure element specifies an explicit role within the table container'
2916
+ },
2917
+ aria_child_valid: {
2918
+ variable: false,
2919
+ quality: 1,
2920
+ what: 'Child element has a role not allowed for the role of the parent'
2901
2921
  }
2902
2922
  },
2903
2923
  nuVal: {
@@ -3020,11 +3040,6 @@ exports.issues = {
3020
3040
  variable: false,
3021
3041
  quality: 1,
3022
3042
  what: 'ARIA attribute is invalid for the role of its element'
3023
- },
3024
- aria_attribute_value_valid: {
3025
- variable: false,
3026
- quality: 1,
3027
- what: 'Value of an attribute on the element is not valid'
3028
3043
  }
3029
3044
  },
3030
3045
  nuVal: {
@@ -3043,16 +3058,6 @@ exports.issues = {
3043
3058
  quality: 1,
3044
3059
  what: 'Attribute not allowed on this element'
3045
3060
  },
3046
- '^Bad value .* for attribute .+ on element .+$': {
3047
- variable: true,
3048
- quality: 1,
3049
- what: 'Attribute on this element has an invalid value'
3050
- },
3051
- '^Bad value .+ for the attribute .+$': {
3052
- variable: true,
3053
- quality: 1,
3054
- what: 'Attribute has an invalid value'
3055
- },
3056
3061
  '^Attribute .+ not allowed here.*$': {
3057
3062
  variable: true,
3058
3063
  quality: 1,
@@ -3063,11 +3068,6 @@ exports.issues = {
3063
3068
  quality: 1,
3064
3069
  what: 'Attribute is invalidly nonserializable'
3065
3070
  },
3066
- '^Bad value for attribute .+ on element .+: Must not be empty.*$': {
3067
- variable: true,
3068
- quality: 1,
3069
- what: 'Attribute has an invalidly empty value'
3070
- },
3071
3071
  '^Attribute .+ is only allowed when .+$': {
3072
3072
  variable: true,
3073
3073
  quality: 1,
@@ -3098,15 +3098,57 @@ exports.issues = {
3098
3098
  quality: 1,
3099
3099
  what: 'Element has a sizes attribute but no srcset attribute'
3100
3100
  },
3101
+ 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be present.': {
3102
+ variable: false,
3103
+ quality: 1,
3104
+ what: 'Element with a srcset attribute with a width has no sizes attribute'
3105
+ },
3106
+ 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be specified.': {
3107
+ variable: false,
3108
+ quality: 1,
3109
+ what: 'Element with a srcset attribute with a width has no valid sizes attribute'
3110
+ }
3111
+ }
3112
+ }
3113
+ },
3114
+ attributeValueBad: {
3115
+ summary: 'attribute value invalid',
3116
+ why: 'Item behaves improperly',
3117
+ wcag: '1.3.1',
3118
+ weight: 4,
3119
+ tools: {
3120
+ ibm: {
3121
+ aria_attribute_value_valid: {
3122
+ variable: false,
3123
+ quality: 1,
3124
+ what: 'Value of an attribute on the element is not valid'
3125
+ }
3126
+ },
3127
+ nuVal: {
3128
+ '^Bad value .* for attribute .+ on element .+$': {
3129
+ variable: true,
3130
+ quality: 1,
3131
+ what: 'Attribute on this element has an invalid value'
3132
+ },
3133
+ '^Bad value .+ for the attribute .+$': {
3134
+ variable: true,
3135
+ quality: 1,
3136
+ what: 'Attribute has an invalid value'
3137
+ },
3138
+ '^Bad value for attribute .+ on element .+: Must not be empty.*$': {
3139
+ variable: true,
3140
+ quality: 1,
3141
+ what: 'Attribute has an invalidly empty value'
3142
+ },
3101
3143
  '^Bad value for attribute (?:width|height) on element img: The empty string is not a valid non-negative integer.*$': {
3102
3144
  variable: true,
3103
3145
  quality: 1,
3104
3146
  what: 'Attribute has an empty value'
3105
3147
  },
3106
- '^.+ in an unquoted attribute value. Probable causes: Attributes running together or a URL query string in an unquoted attribute value.*$': {
3107
- variable: true,
3148
+ 'A script element with a defer attribute must not have a type attribute with the value module.': {
3149
+ variable: false,
3108
3150
  quality: 1,
3109
- what: 'Attribute has a value containing invalid punctuation'
3151
+ what: 'script element with a defer attribute has type="module"'
3110
3152
  }
3111
3153
  }
3112
3154
  }
@@ -3132,11 +3174,6 @@ exports.issues = {
3132
3174
  }
3133
3175
  },
3134
3176
  nuVal: {
3135
- '^Element .+ is missing required attribute .+$': {
3136
- variable: true,
3137
- quality: 1,
3138
- what: 'Element is missing a required attribute'
3139
- },
3140
3177
  '^Element image is missing required attribute (?:height|width).*$': {
3141
3178
  variable: true,
3142
3179
  quality: 1,
@@ -3156,6 +3193,11 @@ exports.issues = {
3156
3193
  variable: false,
3157
3194
  quality: 1,
3158
3195
  what: 'source or img element is missing a media or type attribute'
3196
+ },
3197
+ '^Element .+ is missing required attribute .+$': {
3198
+ variable: true,
3199
+ quality: 1,
3200
+ what: 'Element is missing a required attribute'
3159
3201
  }
3160
3202
  }
3161
3203
  }
@@ -4064,7 +4106,7 @@ exports.issues = {
4064
4106
  }
4065
4107
  }
4066
4108
  },
4067
- docType: {
4109
+ docTypeMissing: {
4068
4110
  summary: 'DOCTYPE missing',
4069
4111
  why: 'Browser processes the document improperly',
4070
4112
  wcag: '1.3.1',
@@ -4087,6 +4129,22 @@ exports.issues = {
4087
4129
  }
4088
4130
  }
4089
4131
  },
4132
+ docTypeBad: {
4133
+ summary: 'DOCTYPE invalid',
4134
+ why: 'Browser processes the document improperly',
4135
+ wcag: '1.3.1',
4136
+ weight: 3,
4137
+ max: 1,
4138
+ tools: {
4139
+ nuVal: {
4140
+ 'Almost standards mode doctype. Expected <!DOCTYPE html>.': {
4141
+ variable: false,
4142
+ quality: 1,
4143
+ what: 'document type declaration differs from <!DOCTYPE html>'
4144
+ }
4145
+ }
4146
+ }
4147
+ },
4090
4148
  pageTitleBad: {
4091
4149
  summary: 'page title invalid',
4092
4150
  why: 'Browser processes the document improperly',
@@ -5256,6 +5314,13 @@ exports.issues = {
5256
5314
  quality: 1,
5257
5315
  what: 'None of the cells in the table is a header'
5258
5316
  }
5317
+ },
5318
+ ibm: {
5319
+ table_headers_exists: {
5320
+ variable: false,
5321
+ quality: 1,
5322
+ what: 'No cell in the table is a th element or has a scope or headers attribute'
5323
+ }
5259
5324
  }
5260
5325
  }
5261
5326
  },
@@ -5610,6 +5675,11 @@ exports.issues = {
5610
5675
  quality: 1,
5611
5676
  what: 'descendant of an element with a link role has a tabindex attribute'
5612
5677
  },
5678
+ 'An element with the attribute tabindex must not appear as a descendant of the button element.': {
5679
+ variable: false,
5680
+ quality: 1,
5681
+ what: 'descendant of a button element has a tabindex attribute'
5682
+ },
5613
5683
  'An element with the attribute tabindex must not appear as a descendant of an element with the attribute role=button.': {
5614
5684
  variable: false,
5615
5685
  quality: 1,
@@ -7965,24 +8035,102 @@ exports.issues = {
7965
8035
  }
7966
8036
  }
7967
8037
  },
7968
- parseError: {
7969
- summary: 'code invalid',
7970
- why: 'Document contains invalid code',
8038
+ characterBad: {
8039
+ summary: 'invalid character',
8040
+ why: 'Invalid character makes the document behave correctly',
7971
8041
  wcag: '4.1',
7972
8042
  weight: 3,
7973
8043
  tools: {
7974
- ibm: {
7975
- aria_child_valid: {
8044
+ nuVal: {
8045
+ '^Bad value [^\ufffd]+ Tab, new line or carriage return found.*$': {
8046
+ variable: true,
8047
+ quality: 1,
8048
+ what: 'Attribute value contains an illegal spacing character'
8049
+ },
8050
+ '^Bad character . after <. Probable cause: Unescaped <. Try escaping it as &lt;.*$': {
8051
+ variable: true,
8052
+ quality: 1,
8053
+ what: 'Left angle bracket is followed by an invalid character'
8054
+ },
8055
+ '^Saw .+ when expecting an attribute name. Probable cause: (?:.+ missing|Missing .+) immediately before.*$': {
8056
+ variable: true,
8057
+ quality: 1,
8058
+ what: 'Invalid character appears where an attribute name must appear'
8059
+ },
8060
+ '^Bad element name .*: Code point .* is not allowed*$': {
8061
+ variable: true,
8062
+ quality: 1,
8063
+ what: 'Element name contains an invalid character'
8064
+ },
8065
+ '^Bad value .* for attribute href on element .+: Illegal character in path segment: .+ is not allowed.*$': {
8066
+ variable: true,
8067
+ quality: 1,
8068
+ what: 'href attribute path value contains an invalid character in a segment'
8069
+ },
8070
+ '^Bad value .* for attribute src on element .+: Illegal character in path segment: .+ is not allowed.*$': {
8071
+ variable: true,
8072
+ quality: 1,
8073
+ what: 'src attribute path value contains an invalid character in a segment'
8074
+ },
8075
+ '^Bad value .* for attribute href on element .+: Illegal character in query: .+ is not allowed.*$': {
8076
+ variable: true,
8077
+ quality: 1,
8078
+ what: 'href attribute query value contains an invalid character'
8079
+ },
8080
+ '^Bad value .* for attribute src on element .+: Illegal character in query: .+ is not allowed.*$': {
8081
+ variable: true,
8082
+ quality: 1,
8083
+ what: 'src attribute query value contains an invalid character'
8084
+ },
8085
+ '^Bad value .+ for attribute src on element .+: Tab, new line or carriage return found.*$': {
8086
+ variable: true,
8087
+ quality: 1,
8088
+ what: 'src attribute value contains a tab, newline, or return character'
8089
+ },
8090
+ 'Non-space character inside noscript inside head.': {
7976
8091
  variable: false,
7977
8092
  quality: 1,
7978
- what: 'Child element has a role not allowed for the role of the parent'
8093
+ what: 'noscript element inside the head element has a nonspace text-node child'
8094
+ },
8095
+ '^.+ in an unquoted attribute value. Probable causes: Attributes running together or a URL query string in an unquoted attribute value.*$': {
8096
+ variable: true,
8097
+ quality: 1,
8098
+ what: 'Attribute has a value containing invalid punctuation'
8099
+ }
8100
+ }
8101
+ }
8102
+ },
8103
+ textContentBad: {
8104
+ summary: 'element text content invalid',
8105
+ why: 'User may be unable to read all the document text',
8106
+ wcag: '4.1',
8107
+ weight: 3,
8108
+ tools: {
8109
+ nuVal: {
8110
+ 'Text run is not in Unicode Normalization Form C.': {
8111
+ variable: false,
8112
+ quality: 1,
8113
+ what: 'Text run is not in Unicode Normalization Form C.'
7979
8114
  },
7980
- Rpt_Aria_InvalidTabindexForActivedescendant: {
8115
+ '^The text content of element .+ was not in the required format: Expected .+ but found .+ instead.*$': {
8116
+ variable: true,
8117
+ quality: 1,
8118
+ what: 'Element has text content with invalid format'
8119
+ },
8120
+ 'The text content of element time was not in the required format: The literal did not satisfy the time-datetime format.': {
7981
8121
  variable: false,
7982
8122
  quality: 1,
7983
- what: 'Element with an aria-activedescendant attribute has no nonpositive tabindex attribute'
8123
+ what: 'time element has text content that is not in the time-datetime format'
7984
8124
  }
7985
- },
8125
+ }
8126
+ }
8127
+ },
8128
+ parseError: {
8129
+ summary: 'code invalid',
8130
+ why: 'Invalid code in the document may prevent a helper from working',
8131
+ wcag: '4.1',
8132
+ weight: 3,
8133
+ tools: {
7986
8134
  nuVal: {
7987
8135
  '^End tag .+ did not match the name of the current open element (.+).*$': {
7988
8136
  variable: true,
@@ -7999,26 +8147,11 @@ exports.issues = {
7999
8147
  quality: 1,
8000
8148
  what: 'No space between attributes'
8001
8149
  },
8002
- '^Bad value [^\ufffd]+ Tab, new line or carriage return found.*$': {
8003
- variable: true,
8004
- quality: 1,
8005
- what: 'Attribute value contains an illegal spacing character'
8006
- },
8007
8150
  'Saw <?. Probable cause: Attempt to use an XML processing instruction in HTML. (XML processing instructions are not supported in HTML.)': {
8008
8151
  variable: false,
8009
8152
  quality: 1,
8010
8153
  what: 'Left angle bracket is followed by a question mark'
8011
8154
  },
8012
- '^Bad character . after <. Probable cause: Unescaped <. Try escaping it as &lt;.*$': {
8013
- variable: true,
8014
- quality: 1,
8015
- what: 'Left angle bracket is followed by an invalid character'
8016
- },
8017
- 'Almost standards mode doctype. Expected <!DOCTYPE html>.': {
8018
- variable: false,
8019
- quality: 1,
8020
- what: 'document type declaration differs from <!DOCTYPE html>'
8021
- },
8022
8155
  '^The aria-hidden attribute must not be specified on the .+ element.*$': {
8023
8156
  variable: true,
8024
8157
  quality: 1,
@@ -8044,11 +8177,6 @@ exports.issues = {
8044
8177
  quality: 1,
8045
8178
  what: 'Comment is nested within a comment'
8046
8179
  },
8047
- '^Saw .+ when expecting an attribute name. Probable cause: (?:.+ missing|Missing .+) immediately before.*$': {
8048
- variable: true,
8049
- quality: 1,
8050
- what: 'Invalid character appears where an attribute name must appear'
8051
- },
8052
8180
  'The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment.': {
8053
8181
  variable: false,
8054
8182
  quality: 1,
@@ -8069,11 +8197,6 @@ exports.issues = {
8069
8197
  quality: 1,
8070
8198
  what: 'Invalid element name'
8071
8199
  },
8072
- '^Bad element name .*: Code point .* is not allowed*$': {
8073
- variable: true,
8074
- quality: 1,
8075
- what: 'Element name contains an invalid character'
8076
- },
8077
8200
  '^Forbidden code point U+.+$': {
8078
8201
  variable: true,
8079
8202
  quality: 1,
@@ -8084,11 +8207,6 @@ exports.issues = {
8084
8207
  quality: 1,
8085
8208
  what: 'Encoding declaration disagrees with the actual encoding of the page'
8086
8209
  },
8087
- 'Text run is not in Unicode Normalization Form C.': {
8088
- variable: false,
8089
- quality: 1,
8090
- what: 'Text run is not in Unicode Normalization Form C.'
8091
- },
8092
8210
  'Quote \" in attribute name. Probable cause: Matching quote missing somewhere earlier.': {
8093
8211
  variable: false,
8094
8212
  quality: 1,
@@ -8099,41 +8217,11 @@ exports.issues = {
8099
8217
  quality: 1,
8100
8218
  what: 'script element has an async attribute but has no src or value=module attribute'
8101
8219
  },
8102
- '^Bad value .* for attribute href on element .+: Illegal character in path segment: .+ is not allowed.*$': {
8103
- variable: true,
8104
- quality: 1,
8105
- what: 'href attribute path value contains an invalid character in a segment'
8106
- },
8107
- '^Bad value .* for attribute src on element .+: Illegal character in path segment: .+ is not allowed.*$': {
8108
- variable: true,
8109
- quality: 1,
8110
- what: 'src attribute path value contains an invalid character in a segment'
8111
- },
8112
- '^Bad value .* for attribute href on element .+: Illegal character in query: .+ is not allowed.*$': {
8113
- variable: true,
8114
- quality: 1,
8115
- what: 'href attribute query value contains an invalid character'
8116
- },
8117
- '^Bad value .* for attribute src on element .+: Illegal character in query: .+ is not allowed.*$': {
8118
- variable: true,
8119
- quality: 1,
8120
- what: 'src attribute query value contains an invalid character'
8121
- },
8122
- '^Bad value .+ for attribute src on element .+: Tab, new line or carriage return found.*$': {
8123
- variable: true,
8124
- quality: 1,
8125
- what: 'src attribute value contains a tab, newline, or return character'
8126
- },
8127
8220
  '^Text not allowed in element .+ in this context.*$': {
8128
8221
  variable: true,
8129
8222
  quality: 1,
8130
8223
  what: 'Element contains text, which is not allowed here'
8131
8224
  },
8132
- 'Element source is missing required attribute srcset.': {
8133
- variable: false,
8134
- quality: 1,
8135
- what: 'source element has no srcset attribute'
8136
- },
8137
8225
  '^The .+ element must not appear as a descendant of the .+ element.*$': {
8138
8226
  variable: true,
8139
8227
  quality: 1,
@@ -8149,41 +8237,11 @@ exports.issues = {
8149
8237
  quality: 1,
8150
8238
  what: 'option element has a nonempty value'
8151
8239
  },
8152
- 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be present.': {
8153
- variable: false,
8154
- quality: 1,
8155
- what: 'Element with a srcset attribute with a width has no sizes attribute'
8156
- },
8157
- 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be specified.': {
8158
- variable: false,
8159
- quality: 1,
8160
- what: 'Element with a srcset attribute with a width has no valid sizes attribute'
8161
- },
8162
- '^The text content of element .+ was not in the required format: Expected .+ but found .+ instead.*$': {
8163
- variable: true,
8164
- quality: 1,
8165
- what: 'Element has text content with invalid format'
8166
- },
8167
8240
  'Element script must not have attribute charset unless attribute src is also specified.': {
8168
8241
  variable: false,
8169
8242
  quality: 1,
8170
8243
  what: 'script element has a charset attribute but no src attribute'
8171
8244
  },
8172
- 'The text content of element time was not in the required format: The literal did not satisfy the time-datetime format.': {
8173
- variable: false,
8174
- quality: 1,
8175
- what: 'time element has text content that is not in the time-datetime format'
8176
- },
8177
- 'A script element with a defer attribute must not have a type attribute with the value module.': {
8178
- variable: false,
8179
- quality: 1,
8180
- what: 'script element with a defer attribute has type="module"'
8181
- },
8182
- 'Non-space character inside noscript inside head.': {
8183
- variable: false,
8184
- quality: 1,
8185
- what: 'noscript element inside the head element has a nonspace text-node child'
8186
- },
8187
8245
  '^java.util.concurrent.TimeoutException: Idle timeout expired: .+ ms.*$': {
8188
8246
  variable: true,
8189
8247
  quality: 1,
@@ -8221,9 +8279,9 @@ exports.issues = {
8221
8279
  }
8222
8280
  }
8223
8281
  },
8224
- encodingBad: {
8282
+ encodingPrivate: {
8225
8283
  summary: 'text in Private Use Area',
8226
- why: 'Document contains invalid code',
8284
+ why: 'User cannot read all of the text',
8227
8285
  wcag: '3.1.3',
8228
8286
  weight: 4,
8229
8287
  max: 1,
@@ -0,0 +1,300 @@
1
+ /*
2
+ tsp37
3
+ Testilo score proc 37
4
+
5
+ Computes target score data and adds them to a ts37 report.
6
+ */
7
+
8
+ // IMPORTS
9
+
10
+ const {issues} = require('./tic38');
11
+
12
+ // CONSTANTS
13
+
14
+ // ID of this proc.
15
+ const scoreProcID = 'tsp38';
16
+ // Latency weight (how much each second of excess latency adds to the score).
17
+ const latencyWeight = 1;
18
+ // Normal latency (6 visits, with 1.5 second per visit).
19
+ const normalLatency = 9;
20
+ // Prevention weight (how much each prevention adds to the score).
21
+ const preventionWeight = 300;
22
+ // Maximum instance count addition weight (divisor of max).
23
+ const maxWeight = 30;
24
+ // Other weights.
25
+ const severityWeights = [1, 2, 3, 4];
26
+ const toolWeight = 0.1;
27
+ const logWeights = {
28
+ logCount: 0.1,
29
+ logSize: 0.002,
30
+ errorLogCount: 0.2,
31
+ errorLogSize: 0.004,
32
+ prohibitedCount: 3,
33
+ visitRejectionCount: 2
34
+ };
35
+ // Initialize a table of tool rules.
36
+ const issueIndex = {};
37
+ // Initialize an array of variably named tool rules.
38
+ const issueMatcher = [];
39
+ // For each issue:
40
+ Object.keys(issues).forEach(issueName => {
41
+ // For each tool with rules belonging to that issue:
42
+ Object.keys(issues[issueName].tools).forEach(toolName => {
43
+ // For each of those rules:
44
+ Object.keys(issues[issueName].tools[toolName]).forEach(ruleID => {
45
+ // Add it to the table of tool rules.
46
+ if (! issueIndex[toolName]) {
47
+ issueIndex[toolName] = {};
48
+ }
49
+ issueIndex[toolName][ruleID] = issueName;
50
+ // If it is variably named:
51
+ if (issues[issueName].tools[toolName][ruleID].variable) {
52
+ // Add it to the array of variably named tool rules.
53
+ issueMatcher.push(ruleID);
54
+ }
55
+ })
56
+ });
57
+ });
58
+
59
+ // FUNCTIONS
60
+
61
+ // Scores a report.
62
+ exports.scorer = report => {
63
+ // If there are any acts in the report:
64
+ const {acts} = report;
65
+ if (Array.isArray(acts) && acts.length) {
66
+ // If any of them are test acts:
67
+ const testActs = acts.filter(act => act.type === 'test');
68
+ if (testActs.length) {
69
+ // Initialize the score data.
70
+ const score = {
71
+ scoreProcID,
72
+ weights: {
73
+ severities: severityWeights,
74
+ tool: toolWeight,
75
+ log: logWeights,
76
+ latency: latencyWeight,
77
+ prevention: preventionWeight,
78
+ maxInstanceCount: maxWeight
79
+ },
80
+ normalLatency,
81
+ summary: {
82
+ total: 0,
83
+ issue: 0,
84
+ solo: 0,
85
+ tool: 0,
86
+ prevention: 0,
87
+ log: 0,
88
+ latency: 0
89
+ },
90
+ details: {
91
+ severity: {
92
+ total: [0, 0, 0, 0],
93
+ byTool: {}
94
+ },
95
+ prevention: {},
96
+ issue: {},
97
+ solo: {},
98
+ tool: {}
99
+ }
100
+ };
101
+ const {summary, details} = score;
102
+ // For each test act:
103
+ testActs.forEach(act => {
104
+ // If the page prevented the tool from operating:
105
+ const {which, standardResult} = act;
106
+ if (! standardResult || standardResult.prevented) {
107
+ // Add this to the score.
108
+ details.prevention[which] = preventionWeight;
109
+ }
110
+ // Otherwise, if a valid standard result exists:
111
+ else if (
112
+ standardResult
113
+ && standardResult.totals
114
+ && standardResult.totals.length === 4
115
+ && standardResult.instances
116
+ ) {
117
+ // Add the severity totals of the tool to the score.
118
+ const {totals} = standardResult;
119
+ details.severity.byTool[which] = totals;
120
+ // Add the severity-weighted tool totals to the score.
121
+ details.tool[which] = totals.reduce(
122
+ (sum, current, index) => sum + severityWeights[index] * current, 0
123
+ );
124
+ // For each instance of the tool:
125
+ standardResult.instances.forEach(instance => {
126
+ // Get the rule ID.
127
+ const {ruleID, ordinalSeverity, count} = instance;
128
+ // If it is not in the table of tool rules:
129
+ let canonicalRuleID = ruleID;
130
+ if (! issueIndex[which][ruleID]) {
131
+ // Convert it to the variably named tool rule that it matches, if any.
132
+ canonicalRuleID = issueMatcher.find(pattern => {
133
+ const patternRE = new RegExp(pattern);
134
+ return patternRE.test(instance.ruleID);
135
+ });
136
+ }
137
+ // If the rule ID belongs to an issue:
138
+ if (canonicalRuleID) {
139
+ // Get the issue.
140
+ const issueName = issueIndex[which][canonicalRuleID];
141
+ // Add the instance to the issue details of the score data.
142
+ if (! details.issue[issueName]) {
143
+ details.issue[issueName] = {
144
+ summary: issues[issueName].summary,
145
+ score: 0,
146
+ maxCount: 0,
147
+ weight: issues[issueName].weight,
148
+ countLimit: issues[issueName].max,
149
+ tools: {}
150
+ };
151
+ if (! details.issue[issueName].countLimit) {
152
+ delete details.issue[issueName].countLimit;
153
+ }
154
+ }
155
+ if (! details.issue[issueName].tools[which]) {
156
+ details.issue[issueName].tools[which] = {};
157
+ }
158
+ if (! details.issue[issueName].tools[which][canonicalRuleID]) {
159
+ const ruleData = issues[issueName].tools[which][canonicalRuleID];
160
+ details.issue[issueName].tools[which][canonicalRuleID] = {
161
+ quality: ruleData.quality,
162
+ what: ruleData.what,
163
+ complaints: {
164
+ countTotal: 0,
165
+ texts: []
166
+ }
167
+ };
168
+ }
169
+ details
170
+ .issue[issueName]
171
+ .tools[which][canonicalRuleID]
172
+ .complaints
173
+ .countTotal += instance.count || 1;
174
+ if (
175
+ ! details
176
+ .issue[issueName]
177
+ .tools[which][canonicalRuleID]
178
+ .complaints
179
+ .texts
180
+ .includes(instance.what)
181
+ ) {
182
+ details
183
+ .issue[issueName]
184
+ .tools[which][canonicalRuleID]
185
+ .complaints
186
+ .texts
187
+ .push(instance.what);
188
+ }
189
+ }
190
+ // Otherwise, i.e. if the rule ID belongs to no issue:
191
+ else {
192
+ // Add the instance to the solo details of the score data.
193
+ if (! details.solo[which]) {
194
+ details.solo[which] = {};
195
+ }
196
+ if (! details.solo[which][ruleID]) {
197
+ details.solo[which][ruleID] = 0;
198
+ }
199
+ details.solo[which][ruleID] += (count || 1) * (ordinalSeverity + 1);
200
+ // Report this.
201
+ console.log(`ERROR: ${instance.ruleID} of ${which} not found in issues`);
202
+ }
203
+ });
204
+ }
205
+ // Otherwise, i.e. if a failed standard result exists:
206
+ else {
207
+ // Add an inferred prevention to the score.
208
+ details.prevention[which] = preventionWeight;
209
+ }
210
+ });
211
+ // For each issue with any complaints:
212
+ Object.keys(details.issue).forEach(issueName => {
213
+ const issueData = details.issue[issueName];
214
+ // For each tool with any complaints for the issue:
215
+ Object.keys(issueData.tools).forEach(toolID => {
216
+ // Get the sum of the quality-weighted counts of its issue rules.
217
+ let weightedCount = 0;
218
+ Object.values(issueData.tools[toolID]).forEach(ruleData => {
219
+ weightedCount += ruleData.quality * ruleData.complaints.countTotal;
220
+ });
221
+ // If the sum exceeds the existing maximum weighted count for the issue:
222
+ if (weightedCount > issueData.maxCount) {
223
+ // Change the maximum count for the issue to the sum.
224
+ issueData.maxCount = weightedCount;
225
+ }
226
+ });
227
+ // Get the score for the issue, including any addition for the instance count limit.
228
+ const maxAddition = issueData.countLimit ? maxWeight / issueData.countLimit : 0;
229
+ issueData.score = Math.round(issueData.weight * issueData.maxCount * (1 + maxAddition));
230
+ });
231
+ // Add the severity detail totals to the score.
232
+ details.severity.total = Object.keys(details.severity.byTool).reduce((severityTotals, toolID) => {
233
+ details.severity.byTool[toolID].forEach((severityScore, index) => {
234
+ severityTotals[index] += severityScore;
235
+ });
236
+ return severityTotals;
237
+ }, details.severity.total);
238
+ // Add the summary issue total to the score.
239
+ summary.issue = Object
240
+ .values(details.issue)
241
+ .reduce((total, current) => total + current.score, 0);
242
+ // Add the summary solo total to the score.
243
+ Object.keys(details.solo).forEach(tool => {
244
+ summary.solo += Object
245
+ .values(details.solo[tool])
246
+ .reduce((total, current) => total + current);
247
+ });
248
+ // Add the summary tool total to the score.
249
+ summary.tool = toolWeight * details.severity.total.reduce(
250
+ (total, current, index) => total + severityWeights[index] * current, 0
251
+ );
252
+ // Add the summary prevention total to the score.
253
+ summary.prevention = Object.values(details.prevention).reduce(
254
+ (total, current) => total + current, 0
255
+ );
256
+ // Add the summary log score to the score.
257
+ const {jobData} = report;
258
+ if (jobData) {
259
+ summary.log = Math.max(0, Math.round(
260
+ logWeights.logCount * jobData.logCount
261
+ + logWeights.logSize * jobData.logSize +
262
+ + logWeights.errorLogCount * jobData.errorLogCount
263
+ + logWeights.errorLogSize * jobData.errorLogSize
264
+ + logWeights.prohibitedCount * jobData.prohibitedCount +
265
+ + logWeights.visitRejectionCount * jobData.visitRejectionCount
266
+ ));
267
+ // Add the summary latency score to the score.
268
+ summary.latency = Math.round(
269
+ latencyWeight * (Math.max(0, jobData.visitLatency - normalLatency))
270
+ );
271
+ }
272
+ // Round the unrounded scores.
273
+ Object.keys(summary).forEach(summaryTypeName => {
274
+ summary[summaryTypeName] = Math.round(summary[summaryTypeName]);
275
+ });
276
+ details.severity.total.forEach((severityTotal, index) => {
277
+ details.severity.total[index] = Math.round(severityTotal);
278
+ });
279
+ // Add the summary total score to the score.
280
+ summary.total = summary.issue
281
+ + summary.solo
282
+ + summary.tool
283
+ + summary.prevention
284
+ + summary.log
285
+ + summary.latency;
286
+ // Add the score to the report or replace the existing score of the report.
287
+ report.score = score;
288
+ }
289
+ // Otherwise, i.e. if none of them is a test act:
290
+ else {
291
+ // Report this.
292
+ console.log('ERROR: No test acts');
293
+ }
294
+ }
295
+ // Otherwise, i.e. if there are no acts in the report:
296
+ else {
297
+ // Report this.
298
+ console.log('ERROR: No acts');
299
+ }
300
+ };
@@ -21,6 +21,8 @@ const normalLatency = 9;
21
21
  const preventionWeight = 300;
22
22
  // Maximum instance count addition weight (divisor of max).
23
23
  const maxWeight = 30;
24
+ // Issue count weight.
25
+ const issueCountWeight = 15;
24
26
  // Other weights.
25
27
  const severityWeights = [1, 2, 3, 4];
26
28
  const toolWeight = 0.1;
@@ -80,6 +82,7 @@ exports.scorer = report => {
80
82
  normalLatency,
81
83
  summary: {
82
84
  total: 0,
85
+ issueCount: 0,
83
86
  issue: 0,
84
87
  solo: 0,
85
88
  tool: 0,
@@ -134,57 +137,59 @@ exports.scorer = report => {
134
137
  return patternRE.test(instance.ruleID);
135
138
  });
136
139
  }
137
- // If the rule ID belongs to an issue:
140
+ // If the rule ID belongs to a non-ignorable issue:
138
141
  if (canonicalRuleID) {
139
142
  // Get the issue.
140
143
  const issueName = issueIndex[which][canonicalRuleID];
141
144
  // Add the instance to the issue details of the score data.
142
- if (! details.issue[issueName]) {
143
- details.issue[issueName] = {
144
- summary: issues[issueName].summary,
145
- score: 0,
146
- maxCount: 0,
147
- weight: issues[issueName].weight,
148
- countLimit: issues[issueName].max,
149
- tools: {}
150
- };
151
- if (! details.issue[issueName].countLimit) {
152
- delete details.issue[issueName].countLimit;
153
- }
154
- }
155
- if (! details.issue[issueName].tools[which]) {
156
- details.issue[issueName].tools[which] = {};
157
- }
158
- if (! details.issue[issueName].tools[which][canonicalRuleID]) {
159
- const ruleData = issues[issueName].tools[which][canonicalRuleID];
160
- details.issue[issueName].tools[which][canonicalRuleID] = {
161
- quality: ruleData.quality,
162
- what: ruleData.what,
163
- complaints: {
164
- countTotal: 0,
165
- texts: []
145
+ if (issueName !== 'ignorable') {
146
+ if (! details.issue[issueName]) {
147
+ details.issue[issueName] = {
148
+ summary: issues[issueName].summary,
149
+ score: 0,
150
+ maxCount: 0,
151
+ weight: issues[issueName].weight,
152
+ countLimit: issues[issueName].max,
153
+ tools: {}
154
+ };
155
+ if (! details.issue[issueName].countLimit) {
156
+ delete details.issue[issueName].countLimit;
166
157
  }
167
- };
168
- }
169
- details
170
- .issue[issueName]
171
- .tools[which][canonicalRuleID]
172
- .complaints
173
- .countTotal += instance.count || 1;
174
- if (
175
- ! details
176
- .issue[issueName]
177
- .tools[which][canonicalRuleID]
178
- .complaints
179
- .texts
180
- .includes(instance.what)
181
- ) {
158
+ }
159
+ if (! details.issue[issueName].tools[which]) {
160
+ details.issue[issueName].tools[which] = {};
161
+ }
162
+ if (! details.issue[issueName].tools[which][canonicalRuleID]) {
163
+ const ruleData = issues[issueName].tools[which][canonicalRuleID];
164
+ details.issue[issueName].tools[which][canonicalRuleID] = {
165
+ quality: ruleData.quality,
166
+ what: ruleData.what,
167
+ complaints: {
168
+ countTotal: 0,
169
+ texts: []
170
+ }
171
+ };
172
+ }
182
173
  details
183
174
  .issue[issueName]
184
175
  .tools[which][canonicalRuleID]
185
176
  .complaints
186
- .texts
187
- .push(instance.what);
177
+ .countTotal += instance.count || 1;
178
+ if (
179
+ ! details
180
+ .issue[issueName]
181
+ .tools[which][canonicalRuleID]
182
+ .complaints
183
+ .texts
184
+ .includes(instance.what)
185
+ ) {
186
+ details
187
+ .issue[issueName]
188
+ .tools[which][canonicalRuleID]
189
+ .complaints
190
+ .texts
191
+ .push(instance.what);
192
+ }
188
193
  }
189
194
  }
190
195
  // Otherwise, i.e. if the rule ID belongs to no issue:
@@ -208,7 +213,7 @@ exports.scorer = report => {
208
213
  details.prevention[which] = preventionWeight;
209
214
  }
210
215
  });
211
- // For each issue with any complaints:
216
+ // For each non-ignorable issue with any complaints:
212
217
  Object.keys(details.issue).forEach(issueName => {
213
218
  const issueData = details.issue[issueName];
214
219
  // For each tool with any complaints for the issue:
@@ -235,6 +240,8 @@ exports.scorer = report => {
235
240
  });
236
241
  return severityTotals;
237
242
  }, details.severity.total);
243
+ // Add the summary issue-count total to the score.
244
+ summary.issueCount = Object.keys(details.issue).length * issueCountWeight;
238
245
  // Add the summary issue total to the score.
239
246
  summary.issue = Object
240
247
  .values(details.issue)
@@ -277,7 +284,8 @@ exports.scorer = report => {
277
284
  details.severity.total[index] = Math.round(severityTotal);
278
285
  });
279
286
  // Add the summary total score to the score.
280
- summary.total = summary.issue
287
+ summary.total = summary.issueCount
288
+ + summary.issue
281
289
  + summary.solo
282
290
  + summary.tool
283
291
  + summary.prevention