vscode-css-languageservice 5.4.1 → 6.0.1

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/README.md +1 -0
  3. package/lib/esm/beautify/beautify-css.js +50 -8
  4. package/lib/esm/cssLanguageService.d.ts +37 -37
  5. package/lib/esm/cssLanguageService.js +72 -75
  6. package/lib/esm/cssLanguageTypes.d.ts +238 -238
  7. package/lib/esm/cssLanguageTypes.js +42 -42
  8. package/lib/esm/data/webCustomData.js +21959 -21965
  9. package/lib/esm/languageFacts/builtinData.js +142 -142
  10. package/lib/esm/languageFacts/colors.js +469 -472
  11. package/lib/esm/languageFacts/dataManager.js +88 -92
  12. package/lib/esm/languageFacts/dataProvider.js +73 -79
  13. package/lib/esm/languageFacts/entry.js +137 -138
  14. package/lib/esm/languageFacts/facts.js +8 -8
  15. package/lib/esm/parser/cssErrors.js +48 -50
  16. package/lib/esm/parser/cssNodes.js +1502 -2019
  17. package/lib/esm/parser/cssParser.js +1534 -1566
  18. package/lib/esm/parser/cssScanner.js +592 -599
  19. package/lib/esm/parser/cssSymbolScope.js +311 -341
  20. package/lib/esm/parser/lessParser.js +714 -740
  21. package/lib/esm/parser/lessScanner.js +57 -78
  22. package/lib/esm/parser/scssErrors.js +18 -20
  23. package/lib/esm/parser/scssParser.js +796 -818
  24. package/lib/esm/parser/scssScanner.js +95 -116
  25. package/lib/esm/services/cssCodeActions.js +77 -81
  26. package/lib/esm/services/cssCompletion.js +1054 -1149
  27. package/lib/esm/services/cssFolding.js +190 -193
  28. package/lib/esm/services/cssFormatter.js +136 -136
  29. package/lib/esm/services/cssHover.js +148 -151
  30. package/lib/esm/services/cssNavigation.js +378 -470
  31. package/lib/esm/services/cssSelectionRange.js +47 -47
  32. package/lib/esm/services/cssValidation.js +41 -44
  33. package/lib/esm/services/lessCompletion.js +378 -397
  34. package/lib/esm/services/lint.js +518 -532
  35. package/lib/esm/services/lintRules.js +76 -83
  36. package/lib/esm/services/lintUtil.js +196 -205
  37. package/lib/esm/services/pathCompletion.js +157 -231
  38. package/lib/esm/services/scssCompletion.js +354 -378
  39. package/lib/esm/services/scssNavigation.js +82 -154
  40. package/lib/esm/services/selectorPrinting.js +492 -536
  41. package/lib/esm/utils/arrays.js +40 -46
  42. package/lib/esm/utils/objects.js +11 -11
  43. package/lib/esm/utils/resources.js +11 -24
  44. package/lib/esm/utils/strings.js +102 -104
  45. package/lib/umd/beautify/beautify-css.js +50 -8
  46. package/lib/umd/cssLanguageService.d.ts +37 -37
  47. package/lib/umd/cssLanguageService.js +99 -102
  48. package/lib/umd/cssLanguageTypes.d.ts +238 -238
  49. package/lib/umd/cssLanguageTypes.js +89 -88
  50. package/lib/umd/data/webCustomData.js +21972 -21978
  51. package/lib/umd/languageFacts/builtinData.js +154 -154
  52. package/lib/umd/languageFacts/colors.js +492 -495
  53. package/lib/umd/languageFacts/dataManager.js +101 -104
  54. package/lib/umd/languageFacts/dataProvider.js +86 -91
  55. package/lib/umd/languageFacts/entry.js +152 -153
  56. package/lib/umd/languageFacts/facts.js +29 -29
  57. package/lib/umd/parser/cssErrors.js +61 -62
  58. package/lib/umd/parser/cssNodes.js +1587 -2034
  59. package/lib/umd/parser/cssParser.js +1547 -1578
  60. package/lib/umd/parser/cssScanner.js +606 -611
  61. package/lib/umd/parser/cssSymbolScope.js +328 -353
  62. package/lib/umd/parser/lessParser.js +727 -752
  63. package/lib/umd/parser/lessScanner.js +70 -90
  64. package/lib/umd/parser/scssErrors.js +31 -32
  65. package/lib/umd/parser/scssParser.js +809 -830
  66. package/lib/umd/parser/scssScanner.js +108 -128
  67. package/lib/umd/services/cssCodeActions.js +90 -93
  68. package/lib/umd/services/cssCompletion.js +1067 -1161
  69. package/lib/umd/services/cssFolding.js +203 -206
  70. package/lib/umd/services/cssFormatter.js +150 -150
  71. package/lib/umd/services/cssHover.js +161 -163
  72. package/lib/umd/services/cssNavigation.js +391 -482
  73. package/lib/umd/services/cssSelectionRange.js +60 -60
  74. package/lib/umd/services/cssValidation.js +54 -56
  75. package/lib/umd/services/lessCompletion.js +391 -409
  76. package/lib/umd/services/lint.js +531 -544
  77. package/lib/umd/services/lintRules.js +91 -95
  78. package/lib/umd/services/lintUtil.js +210 -218
  79. package/lib/umd/services/pathCompletion.js +171 -244
  80. package/lib/umd/services/scssCompletion.js +367 -390
  81. package/lib/umd/services/scssNavigation.js +95 -166
  82. package/lib/umd/services/selectorPrinting.js +510 -550
  83. package/lib/umd/utils/arrays.js +55 -61
  84. package/lib/umd/utils/objects.js +25 -25
  85. package/lib/umd/utils/resources.js +26 -39
  86. package/lib/umd/utils/strings.js +120 -122
  87. package/package.json +11 -11
@@ -1,532 +1,518 @@
1
- /*---------------------------------------------------------------------------------------------
2
- * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the MIT License. See License.txt in the project root for license information.
4
- *--------------------------------------------------------------------------------------------*/
5
- 'use strict';
6
- import * as nls from 'vscode-nls';
7
- import * as languageFacts from '../languageFacts/facts';
8
- import * as nodes from '../parser/cssNodes';
9
- import { union } from '../utils/arrays';
10
- import { Rules, Settings } from './lintRules';
11
- import calculateBoxModel, { Element } from './lintUtil';
12
- var localize = nls.loadMessageBundle();
13
- var NodesByRootMap = /** @class */ (function () {
14
- function NodesByRootMap() {
15
- this.data = {};
16
- }
17
- NodesByRootMap.prototype.add = function (root, name, node) {
18
- var entry = this.data[root];
19
- if (!entry) {
20
- entry = { nodes: [], names: [] };
21
- this.data[root] = entry;
22
- }
23
- entry.names.push(name);
24
- if (node) {
25
- entry.nodes.push(node);
26
- }
27
- };
28
- return NodesByRootMap;
29
- }());
30
- var LintVisitor = /** @class */ (function () {
31
- function LintVisitor(document, settings, cssDataManager) {
32
- var _this = this;
33
- this.cssDataManager = cssDataManager;
34
- this.warnings = [];
35
- this.settings = settings;
36
- this.documentText = document.getText();
37
- this.keyframes = new NodesByRootMap();
38
- this.validProperties = {};
39
- var properties = settings.getSetting(Settings.ValidProperties);
40
- if (Array.isArray(properties)) {
41
- properties.forEach(function (p) {
42
- if (typeof p === 'string') {
43
- var name = p.trim().toLowerCase();
44
- if (name.length) {
45
- _this.validProperties[name] = true;
46
- }
47
- }
48
- });
49
- }
50
- }
51
- LintVisitor.entries = function (node, document, settings, cssDataManager, entryFilter) {
52
- var visitor = new LintVisitor(document, settings, cssDataManager);
53
- node.acceptVisitor(visitor);
54
- visitor.completeValidations();
55
- return visitor.getEntries(entryFilter);
56
- };
57
- LintVisitor.prototype.isValidPropertyDeclaration = function (element) {
58
- var propertyName = element.fullPropertyName;
59
- return this.validProperties[propertyName];
60
- };
61
- LintVisitor.prototype.fetch = function (input, s) {
62
- var elements = [];
63
- for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
64
- var curr = input_1[_i];
65
- if (curr.fullPropertyName === s) {
66
- elements.push(curr);
67
- }
68
- }
69
- return elements;
70
- };
71
- LintVisitor.prototype.fetchWithValue = function (input, s, v) {
72
- var elements = [];
73
- for (var _i = 0, input_2 = input; _i < input_2.length; _i++) {
74
- var inputElement = input_2[_i];
75
- if (inputElement.fullPropertyName === s) {
76
- var expression = inputElement.node.getValue();
77
- if (expression && this.findValueInExpression(expression, v)) {
78
- elements.push(inputElement);
79
- }
80
- }
81
- }
82
- return elements;
83
- };
84
- LintVisitor.prototype.findValueInExpression = function (expression, v) {
85
- var found = false;
86
- expression.accept(function (node) {
87
- if (node.type === nodes.NodeType.Identifier && node.matches(v)) {
88
- found = true;
89
- }
90
- return !found;
91
- });
92
- return found;
93
- };
94
- LintVisitor.prototype.getEntries = function (filter) {
95
- if (filter === void 0) { filter = (nodes.Level.Warning | nodes.Level.Error); }
96
- return this.warnings.filter(function (entry) {
97
- return (entry.getLevel() & filter) !== 0;
98
- });
99
- };
100
- LintVisitor.prototype.addEntry = function (node, rule, details) {
101
- var entry = new nodes.Marker(node, rule, this.settings.getRule(rule), details);
102
- this.warnings.push(entry);
103
- };
104
- LintVisitor.prototype.getMissingNames = function (expected, actual) {
105
- var expectedClone = expected.slice(0); // clone
106
- for (var i = 0; i < actual.length; i++) {
107
- var k = expectedClone.indexOf(actual[i]);
108
- if (k !== -1) {
109
- expectedClone[k] = null;
110
- }
111
- }
112
- var result = null;
113
- for (var i = 0; i < expectedClone.length; i++) {
114
- var curr = expectedClone[i];
115
- if (curr) {
116
- if (result === null) {
117
- result = localize('namelist.single', "'{0}'", curr);
118
- }
119
- else {
120
- result = localize('namelist.concatenated', "{0}, '{1}'", result, curr);
121
- }
122
- }
123
- }
124
- return result;
125
- };
126
- LintVisitor.prototype.visitNode = function (node) {
127
- switch (node.type) {
128
- case nodes.NodeType.UnknownAtRule:
129
- return this.visitUnknownAtRule(node);
130
- case nodes.NodeType.Keyframe:
131
- return this.visitKeyframe(node);
132
- case nodes.NodeType.FontFace:
133
- return this.visitFontFace(node);
134
- case nodes.NodeType.Ruleset:
135
- return this.visitRuleSet(node);
136
- case nodes.NodeType.SimpleSelector:
137
- return this.visitSimpleSelector(node);
138
- case nodes.NodeType.Function:
139
- return this.visitFunction(node);
140
- case nodes.NodeType.NumericValue:
141
- return this.visitNumericValue(node);
142
- case nodes.NodeType.Import:
143
- return this.visitImport(node);
144
- case nodes.NodeType.HexColorValue:
145
- return this.visitHexColorValue(node);
146
- case nodes.NodeType.Prio:
147
- return this.visitPrio(node);
148
- case nodes.NodeType.IdentifierSelector:
149
- return this.visitIdentifierSelector(node);
150
- }
151
- return true;
152
- };
153
- LintVisitor.prototype.completeValidations = function () {
154
- this.validateKeyframes();
155
- };
156
- LintVisitor.prototype.visitUnknownAtRule = function (node) {
157
- var atRuleName = node.getChild(0);
158
- if (!atRuleName) {
159
- return false;
160
- }
161
- var atDirective = this.cssDataManager.getAtDirective(atRuleName.getText());
162
- if (atDirective) {
163
- return false;
164
- }
165
- this.addEntry(atRuleName, Rules.UnknownAtRules, "Unknown at rule ".concat(atRuleName.getText()));
166
- return true;
167
- };
168
- LintVisitor.prototype.visitKeyframe = function (node) {
169
- var keyword = node.getKeyword();
170
- if (!keyword) {
171
- return false;
172
- }
173
- var text = keyword.getText();
174
- this.keyframes.add(node.getName(), text, (text !== '@keyframes') ? keyword : null);
175
- return true;
176
- };
177
- LintVisitor.prototype.validateKeyframes = function () {
178
- // @keyframe and it's vendor specific alternatives
179
- // @keyframe should be included
180
- var expected = ['@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes'];
181
- for (var name in this.keyframes.data) {
182
- var actual = this.keyframes.data[name].names;
183
- var needsStandard = (actual.indexOf('@keyframes') === -1);
184
- if (!needsStandard && actual.length === 1) {
185
- continue; // only the non-vendor specific keyword is used, that's fine, no warning
186
- }
187
- var missingVendorSpecific = this.getMissingNames(expected, actual);
188
- if (missingVendorSpecific || needsStandard) {
189
- for (var _i = 0, _a = this.keyframes.data[name].nodes; _i < _a.length; _i++) {
190
- var node = _a[_i];
191
- if (needsStandard) {
192
- var message = localize('keyframes.standardrule.missing', "Always define standard rule '@keyframes' when defining keyframes.");
193
- this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
194
- }
195
- if (missingVendorSpecific) {
196
- var message = localize('keyframes.vendorspecific.missing', "Always include all vendor specific rules: Missing: {0}", missingVendorSpecific);
197
- this.addEntry(node, Rules.AllVendorPrefixes, message);
198
- }
199
- }
200
- }
201
- }
202
- return true;
203
- };
204
- LintVisitor.prototype.visitSimpleSelector = function (node) {
205
- /////////////////////////////////////////////////////////////
206
- // Lint - The universal selector (*) is known to be slow.
207
- /////////////////////////////////////////////////////////////
208
- var firstChar = this.documentText.charAt(node.offset);
209
- if (node.length === 1 && firstChar === '*') {
210
- this.addEntry(node, Rules.UniversalSelector);
211
- }
212
- return true;
213
- };
214
- LintVisitor.prototype.visitIdentifierSelector = function (node) {
215
- /////////////////////////////////////////////////////////////
216
- // Lint - Avoid id selectors
217
- /////////////////////////////////////////////////////////////
218
- this.addEntry(node, Rules.AvoidIdSelector);
219
- return true;
220
- };
221
- LintVisitor.prototype.visitImport = function (node) {
222
- /////////////////////////////////////////////////////////////
223
- // Lint - Import statements shouldn't be used, because they aren't offering parallel downloads.
224
- /////////////////////////////////////////////////////////////
225
- this.addEntry(node, Rules.ImportStatemement);
226
- return true;
227
- };
228
- LintVisitor.prototype.visitRuleSet = function (node) {
229
- /////////////////////////////////////////////////////////////
230
- // Lint - Don't use empty rulesets.
231
- /////////////////////////////////////////////////////////////
232
- var declarations = node.getDeclarations();
233
- if (!declarations) {
234
- // syntax error
235
- return false;
236
- }
237
- if (!declarations.hasChildren()) {
238
- this.addEntry(node.getSelectors(), Rules.EmptyRuleSet);
239
- }
240
- var propertyTable = [];
241
- for (var _i = 0, _a = declarations.getChildren(); _i < _a.length; _i++) {
242
- var element = _a[_i];
243
- if (element instanceof nodes.Declaration) {
244
- propertyTable.push(new Element(element));
245
- }
246
- }
247
- /////////////////////////////////////////////////////////////
248
- // the rule warns when it finds:
249
- // width being used with border, border-left, border-right, padding, padding-left, or padding-right
250
- // height being used with border, border-top, border-bottom, padding, padding-top, or padding-bottom
251
- // No error when box-sizing property is specified, as it assumes the user knows what he's doing.
252
- // see https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size
253
- /////////////////////////////////////////////////////////////
254
- var boxModel = calculateBoxModel(propertyTable);
255
- if (boxModel.width) {
256
- var properties = [];
257
- if (boxModel.right.value) {
258
- properties = union(properties, boxModel.right.properties);
259
- }
260
- if (boxModel.left.value) {
261
- properties = union(properties, boxModel.left.properties);
262
- }
263
- if (properties.length !== 0) {
264
- for (var _b = 0, properties_1 = properties; _b < properties_1.length; _b++) {
265
- var item = properties_1[_b];
266
- this.addEntry(item.node, Rules.BewareOfBoxModelSize);
267
- }
268
- this.addEntry(boxModel.width.node, Rules.BewareOfBoxModelSize);
269
- }
270
- }
271
- if (boxModel.height) {
272
- var properties = [];
273
- if (boxModel.top.value) {
274
- properties = union(properties, boxModel.top.properties);
275
- }
276
- if (boxModel.bottom.value) {
277
- properties = union(properties, boxModel.bottom.properties);
278
- }
279
- if (properties.length !== 0) {
280
- for (var _c = 0, properties_2 = properties; _c < properties_2.length; _c++) {
281
- var item = properties_2[_c];
282
- this.addEntry(item.node, Rules.BewareOfBoxModelSize);
283
- }
284
- this.addEntry(boxModel.height.node, Rules.BewareOfBoxModelSize);
285
- }
286
- }
287
- /////////////////////////////////////////////////////////////
288
- // Properties ignored due to display
289
- /////////////////////////////////////////////////////////////
290
- // With 'display: inline-block', 'float' has no effect
291
- var displayElems = this.fetchWithValue(propertyTable, 'display', 'inline-block');
292
- if (displayElems.length > 0) {
293
- var elem = this.fetch(propertyTable, 'float');
294
- for (var index = 0; index < elem.length; index++) {
295
- var node_1 = elem[index].node;
296
- var value = node_1.getValue();
297
- if (value && !value.matches('none')) {
298
- this.addEntry(node_1, Rules.PropertyIgnoredDueToDisplay, localize('rule.propertyIgnoredDueToDisplayInlineBlock', "inline-block is ignored due to the float. If 'float' has a value other than 'none', the box is floated and 'display' is treated as 'block'"));
299
- }
300
- }
301
- }
302
- // With 'display: block', 'vertical-align' has no effect
303
- displayElems = this.fetchWithValue(propertyTable, 'display', 'block');
304
- if (displayElems.length > 0) {
305
- var elem = this.fetch(propertyTable, 'vertical-align');
306
- for (var index = 0; index < elem.length; index++) {
307
- this.addEntry(elem[index].node, Rules.PropertyIgnoredDueToDisplay, localize('rule.propertyIgnoredDueToDisplayBlock', "Property is ignored due to the display. With 'display: block', vertical-align should not be used."));
308
- }
309
- }
310
- /////////////////////////////////////////////////////////////
311
- // Avoid 'float'
312
- /////////////////////////////////////////////////////////////
313
- var elements = this.fetch(propertyTable, 'float');
314
- for (var index = 0; index < elements.length; index++) {
315
- var element = elements[index];
316
- if (!this.isValidPropertyDeclaration(element)) {
317
- this.addEntry(element.node, Rules.AvoidFloat);
318
- }
319
- }
320
- /////////////////////////////////////////////////////////////
321
- // Don't use duplicate declarations.
322
- /////////////////////////////////////////////////////////////
323
- for (var i = 0; i < propertyTable.length; i++) {
324
- var element = propertyTable[i];
325
- if (element.fullPropertyName !== 'background' && !this.validProperties[element.fullPropertyName]) {
326
- var value = element.node.getValue();
327
- if (value && this.documentText.charAt(value.offset) !== '-') {
328
- var elements_1 = this.fetch(propertyTable, element.fullPropertyName);
329
- if (elements_1.length > 1) {
330
- for (var k = 0; k < elements_1.length; k++) {
331
- var value_1 = elements_1[k].node.getValue();
332
- if (value_1 && this.documentText.charAt(value_1.offset) !== '-' && elements_1[k] !== element) {
333
- this.addEntry(element.node, Rules.DuplicateDeclarations);
334
- }
335
- }
336
- }
337
- }
338
- }
339
- }
340
- /////////////////////////////////////////////////////////////
341
- // Unknown propery & When using a vendor-prefixed gradient, make sure to use them all.
342
- /////////////////////////////////////////////////////////////
343
- var isExportBlock = node.getSelectors().matches(":export");
344
- if (!isExportBlock) {
345
- var propertiesBySuffix = new NodesByRootMap();
346
- var containsUnknowns = false;
347
- for (var _d = 0, propertyTable_1 = propertyTable; _d < propertyTable_1.length; _d++) {
348
- var element = propertyTable_1[_d];
349
- var decl = element.node;
350
- if (this.isCSSDeclaration(decl)) {
351
- var name = element.fullPropertyName;
352
- var firstChar = name.charAt(0);
353
- if (firstChar === '-') {
354
- if (name.charAt(1) !== '-') { // avoid css variables
355
- if (!this.cssDataManager.isKnownProperty(name) && !this.validProperties[name]) {
356
- this.addEntry(decl.getProperty(), Rules.UnknownVendorSpecificProperty);
357
- }
358
- var nonPrefixedName = decl.getNonPrefixedPropertyName();
359
- propertiesBySuffix.add(nonPrefixedName, name, decl.getProperty());
360
- }
361
- }
362
- else {
363
- var fullName = name;
364
- if (firstChar === '*' || firstChar === '_') {
365
- this.addEntry(decl.getProperty(), Rules.IEStarHack);
366
- name = name.substr(1);
367
- }
368
- // _property and *property might be contributed via custom data
369
- if (!this.cssDataManager.isKnownProperty(fullName) && !this.cssDataManager.isKnownProperty(name)) {
370
- if (!this.validProperties[name]) {
371
- this.addEntry(decl.getProperty(), Rules.UnknownProperty, localize('property.unknownproperty.detailed', "Unknown property: '{0}'", decl.getFullPropertyName()));
372
- }
373
- }
374
- propertiesBySuffix.add(name, name, null); // don't pass the node as we don't show errors on the standard
375
- }
376
- }
377
- else {
378
- containsUnknowns = true;
379
- }
380
- }
381
- if (!containsUnknowns) { // don't perform this test if there are
382
- for (var suffix in propertiesBySuffix.data) {
383
- var entry = propertiesBySuffix.data[suffix];
384
- var actual = entry.names;
385
- var needsStandard = this.cssDataManager.isStandardProperty(suffix) && (actual.indexOf(suffix) === -1);
386
- if (!needsStandard && actual.length === 1) {
387
- continue; // only the non-vendor specific rule is used, that's fine, no warning
388
- }
389
- var expected = [];
390
- for (var i = 0, len = LintVisitor.prefixes.length; i < len; i++) {
391
- var prefix = LintVisitor.prefixes[i];
392
- if (this.cssDataManager.isStandardProperty(prefix + suffix)) {
393
- expected.push(prefix + suffix);
394
- }
395
- }
396
- var missingVendorSpecific = this.getMissingNames(expected, actual);
397
- if (missingVendorSpecific || needsStandard) {
398
- for (var _e = 0, _f = entry.nodes; _e < _f.length; _e++) {
399
- var node_2 = _f[_e];
400
- if (needsStandard) {
401
- var message = localize('property.standard.missing', "Also define the standard property '{0}' for compatibility", suffix);
402
- this.addEntry(node_2, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
403
- }
404
- if (missingVendorSpecific) {
405
- var message = localize('property.vendorspecific.missing', "Always include all vendor specific properties: Missing: {0}", missingVendorSpecific);
406
- this.addEntry(node_2, Rules.AllVendorPrefixes, message);
407
- }
408
- }
409
- }
410
- }
411
- }
412
- }
413
- return true;
414
- };
415
- LintVisitor.prototype.visitPrio = function (node) {
416
- /////////////////////////////////////////////////////////////
417
- // Don't use !important
418
- /////////////////////////////////////////////////////////////
419
- this.addEntry(node, Rules.AvoidImportant);
420
- return true;
421
- };
422
- LintVisitor.prototype.visitNumericValue = function (node) {
423
- /////////////////////////////////////////////////////////////
424
- // 0 has no following unit
425
- /////////////////////////////////////////////////////////////
426
- var funcDecl = node.findParent(nodes.NodeType.Function);
427
- if (funcDecl && funcDecl.getName() === 'calc') {
428
- return true;
429
- }
430
- var decl = node.findParent(nodes.NodeType.Declaration);
431
- if (decl) {
432
- var declValue = decl.getValue();
433
- if (declValue) {
434
- var value = node.getValue();
435
- if (!value.unit || languageFacts.units.length.indexOf(value.unit.toLowerCase()) === -1) {
436
- return true;
437
- }
438
- if (parseFloat(value.value) === 0.0 && !!value.unit && !this.validProperties[decl.getFullPropertyName()]) {
439
- this.addEntry(node, Rules.ZeroWithUnit);
440
- }
441
- }
442
- }
443
- return true;
444
- };
445
- LintVisitor.prototype.visitFontFace = function (node) {
446
- var declarations = node.getDeclarations();
447
- if (!declarations) {
448
- // syntax error
449
- return false;
450
- }
451
- var definesSrc = false, definesFontFamily = false;
452
- var containsUnknowns = false;
453
- for (var _i = 0, _a = declarations.getChildren(); _i < _a.length; _i++) {
454
- var node_3 = _a[_i];
455
- if (this.isCSSDeclaration(node_3)) {
456
- var name = node_3.getProperty().getName().toLowerCase();
457
- if (name === 'src') {
458
- definesSrc = true;
459
- }
460
- if (name === 'font-family') {
461
- definesFontFamily = true;
462
- }
463
- }
464
- else {
465
- containsUnknowns = true;
466
- }
467
- }
468
- if (!containsUnknowns && (!definesSrc || !definesFontFamily)) {
469
- this.addEntry(node, Rules.RequiredPropertiesForFontFace);
470
- }
471
- return true;
472
- };
473
- LintVisitor.prototype.isCSSDeclaration = function (node) {
474
- if (node instanceof nodes.Declaration) {
475
- if (!node.getValue()) {
476
- return false;
477
- }
478
- var property = node.getProperty();
479
- if (!property) {
480
- return false;
481
- }
482
- var identifier = property.getIdentifier();
483
- if (!identifier || identifier.containsInterpolation()) {
484
- return false;
485
- }
486
- return true;
487
- }
488
- return false;
489
- };
490
- LintVisitor.prototype.visitHexColorValue = function (node) {
491
- // Rule: #eeff0011 or #eeff00 or #ef01 or #ef0
492
- var length = node.length;
493
- if (length !== 9 && length !== 7 && length !== 5 && length !== 4) {
494
- this.addEntry(node, Rules.HexColorLength);
495
- }
496
- return false;
497
- };
498
- LintVisitor.prototype.visitFunction = function (node) {
499
- var fnName = node.getName().toLowerCase();
500
- var expectedAttrCount = -1;
501
- var actualAttrCount = 0;
502
- switch (fnName) {
503
- case 'rgb(':
504
- case 'hsl(':
505
- expectedAttrCount = 3;
506
- break;
507
- case 'rgba(':
508
- case 'hsla(':
509
- expectedAttrCount = 4;
510
- break;
511
- }
512
- if (expectedAttrCount !== -1) {
513
- node.getArguments().accept(function (n) {
514
- if (n instanceof nodes.BinaryExpression) {
515
- actualAttrCount += 1;
516
- return false;
517
- }
518
- return true;
519
- });
520
- if (actualAttrCount !== expectedAttrCount) {
521
- this.addEntry(node, Rules.ArgsInColorFunction);
522
- }
523
- }
524
- return true;
525
- };
526
- LintVisitor.prefixes = [
527
- '-ms-', '-moz-', '-o-', '-webkit-', // Quite common
528
- // '-xv-', '-atsc-', '-wap-', '-khtml-', 'mso-', 'prince-', '-ah-', '-hp-', '-ro-', '-rim-', '-tc-' // Quite un-common
529
- ];
530
- return LintVisitor;
531
- }());
532
- export { LintVisitor };
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License. See License.txt in the project root for license information.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ 'use strict';
6
+ import * as nls from 'vscode-nls';
7
+ import * as languageFacts from '../languageFacts/facts';
8
+ import * as nodes from '../parser/cssNodes';
9
+ import { union } from '../utils/arrays';
10
+ import { Rules, Settings } from './lintRules';
11
+ import calculateBoxModel, { Element } from './lintUtil';
12
+ const localize = nls.loadMessageBundle();
13
+ class NodesByRootMap {
14
+ constructor() {
15
+ this.data = {};
16
+ }
17
+ add(root, name, node) {
18
+ let entry = this.data[root];
19
+ if (!entry) {
20
+ entry = { nodes: [], names: [] };
21
+ this.data[root] = entry;
22
+ }
23
+ entry.names.push(name);
24
+ if (node) {
25
+ entry.nodes.push(node);
26
+ }
27
+ }
28
+ }
29
+ export class LintVisitor {
30
+ constructor(document, settings, cssDataManager) {
31
+ this.cssDataManager = cssDataManager;
32
+ this.warnings = [];
33
+ this.settings = settings;
34
+ this.documentText = document.getText();
35
+ this.keyframes = new NodesByRootMap();
36
+ this.validProperties = {};
37
+ const properties = settings.getSetting(Settings.ValidProperties);
38
+ if (Array.isArray(properties)) {
39
+ properties.forEach((p) => {
40
+ if (typeof p === 'string') {
41
+ const name = p.trim().toLowerCase();
42
+ if (name.length) {
43
+ this.validProperties[name] = true;
44
+ }
45
+ }
46
+ });
47
+ }
48
+ }
49
+ static entries(node, document, settings, cssDataManager, entryFilter) {
50
+ const visitor = new LintVisitor(document, settings, cssDataManager);
51
+ node.acceptVisitor(visitor);
52
+ visitor.completeValidations();
53
+ return visitor.getEntries(entryFilter);
54
+ }
55
+ isValidPropertyDeclaration(element) {
56
+ const propertyName = element.fullPropertyName;
57
+ return this.validProperties[propertyName];
58
+ }
59
+ fetch(input, s) {
60
+ const elements = [];
61
+ for (const curr of input) {
62
+ if (curr.fullPropertyName === s) {
63
+ elements.push(curr);
64
+ }
65
+ }
66
+ return elements;
67
+ }
68
+ fetchWithValue(input, s, v) {
69
+ const elements = [];
70
+ for (const inputElement of input) {
71
+ if (inputElement.fullPropertyName === s) {
72
+ const expression = inputElement.node.getValue();
73
+ if (expression && this.findValueInExpression(expression, v)) {
74
+ elements.push(inputElement);
75
+ }
76
+ }
77
+ }
78
+ return elements;
79
+ }
80
+ findValueInExpression(expression, v) {
81
+ let found = false;
82
+ expression.accept(node => {
83
+ if (node.type === nodes.NodeType.Identifier && node.matches(v)) {
84
+ found = true;
85
+ }
86
+ return !found;
87
+ });
88
+ return found;
89
+ }
90
+ getEntries(filter = (nodes.Level.Warning | nodes.Level.Error)) {
91
+ return this.warnings.filter(entry => {
92
+ return (entry.getLevel() & filter) !== 0;
93
+ });
94
+ }
95
+ addEntry(node, rule, details) {
96
+ const entry = new nodes.Marker(node, rule, this.settings.getRule(rule), details);
97
+ this.warnings.push(entry);
98
+ }
99
+ getMissingNames(expected, actual) {
100
+ const expectedClone = expected.slice(0); // clone
101
+ for (let i = 0; i < actual.length; i++) {
102
+ const k = expectedClone.indexOf(actual[i]);
103
+ if (k !== -1) {
104
+ expectedClone[k] = null;
105
+ }
106
+ }
107
+ let result = null;
108
+ for (let i = 0; i < expectedClone.length; i++) {
109
+ const curr = expectedClone[i];
110
+ if (curr) {
111
+ if (result === null) {
112
+ result = localize('namelist.single', "'{0}'", curr);
113
+ }
114
+ else {
115
+ result = localize('namelist.concatenated', "{0}, '{1}'", result, curr);
116
+ }
117
+ }
118
+ }
119
+ return result;
120
+ }
121
+ visitNode(node) {
122
+ switch (node.type) {
123
+ case nodes.NodeType.UnknownAtRule:
124
+ return this.visitUnknownAtRule(node);
125
+ case nodes.NodeType.Keyframe:
126
+ return this.visitKeyframe(node);
127
+ case nodes.NodeType.FontFace:
128
+ return this.visitFontFace(node);
129
+ case nodes.NodeType.Ruleset:
130
+ return this.visitRuleSet(node);
131
+ case nodes.NodeType.SimpleSelector:
132
+ return this.visitSimpleSelector(node);
133
+ case nodes.NodeType.Function:
134
+ return this.visitFunction(node);
135
+ case nodes.NodeType.NumericValue:
136
+ return this.visitNumericValue(node);
137
+ case nodes.NodeType.Import:
138
+ return this.visitImport(node);
139
+ case nodes.NodeType.HexColorValue:
140
+ return this.visitHexColorValue(node);
141
+ case nodes.NodeType.Prio:
142
+ return this.visitPrio(node);
143
+ case nodes.NodeType.IdentifierSelector:
144
+ return this.visitIdentifierSelector(node);
145
+ }
146
+ return true;
147
+ }
148
+ completeValidations() {
149
+ this.validateKeyframes();
150
+ }
151
+ visitUnknownAtRule(node) {
152
+ const atRuleName = node.getChild(0);
153
+ if (!atRuleName) {
154
+ return false;
155
+ }
156
+ const atDirective = this.cssDataManager.getAtDirective(atRuleName.getText());
157
+ if (atDirective) {
158
+ return false;
159
+ }
160
+ this.addEntry(atRuleName, Rules.UnknownAtRules, `Unknown at rule ${atRuleName.getText()}`);
161
+ return true;
162
+ }
163
+ visitKeyframe(node) {
164
+ const keyword = node.getKeyword();
165
+ if (!keyword) {
166
+ return false;
167
+ }
168
+ const text = keyword.getText();
169
+ this.keyframes.add(node.getName(), text, (text !== '@keyframes') ? keyword : null);
170
+ return true;
171
+ }
172
+ validateKeyframes() {
173
+ // @keyframe and it's vendor specific alternatives
174
+ // @keyframe should be included
175
+ const expected = ['@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes'];
176
+ for (const name in this.keyframes.data) {
177
+ const actual = this.keyframes.data[name].names;
178
+ const needsStandard = (actual.indexOf('@keyframes') === -1);
179
+ if (!needsStandard && actual.length === 1) {
180
+ continue; // only the non-vendor specific keyword is used, that's fine, no warning
181
+ }
182
+ const missingVendorSpecific = this.getMissingNames(expected, actual);
183
+ if (missingVendorSpecific || needsStandard) {
184
+ for (const node of this.keyframes.data[name].nodes) {
185
+ if (needsStandard) {
186
+ const message = localize('keyframes.standardrule.missing', "Always define standard rule '@keyframes' when defining keyframes.");
187
+ this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
188
+ }
189
+ if (missingVendorSpecific) {
190
+ const message = localize('keyframes.vendorspecific.missing', "Always include all vendor specific rules: Missing: {0}", missingVendorSpecific);
191
+ this.addEntry(node, Rules.AllVendorPrefixes, message);
192
+ }
193
+ }
194
+ }
195
+ }
196
+ return true;
197
+ }
198
+ visitSimpleSelector(node) {
199
+ /////////////////////////////////////////////////////////////
200
+ // Lint - The universal selector (*) is known to be slow.
201
+ /////////////////////////////////////////////////////////////
202
+ const firstChar = this.documentText.charAt(node.offset);
203
+ if (node.length === 1 && firstChar === '*') {
204
+ this.addEntry(node, Rules.UniversalSelector);
205
+ }
206
+ return true;
207
+ }
208
+ visitIdentifierSelector(node) {
209
+ /////////////////////////////////////////////////////////////
210
+ // Lint - Avoid id selectors
211
+ /////////////////////////////////////////////////////////////
212
+ this.addEntry(node, Rules.AvoidIdSelector);
213
+ return true;
214
+ }
215
+ visitImport(node) {
216
+ /////////////////////////////////////////////////////////////
217
+ // Lint - Import statements shouldn't be used, because they aren't offering parallel downloads.
218
+ /////////////////////////////////////////////////////////////
219
+ this.addEntry(node, Rules.ImportStatemement);
220
+ return true;
221
+ }
222
+ visitRuleSet(node) {
223
+ /////////////////////////////////////////////////////////////
224
+ // Lint - Don't use empty rulesets.
225
+ /////////////////////////////////////////////////////////////
226
+ const declarations = node.getDeclarations();
227
+ if (!declarations) {
228
+ // syntax error
229
+ return false;
230
+ }
231
+ if (!declarations.hasChildren()) {
232
+ this.addEntry(node.getSelectors(), Rules.EmptyRuleSet);
233
+ }
234
+ const propertyTable = [];
235
+ for (const element of declarations.getChildren()) {
236
+ if (element instanceof nodes.Declaration) {
237
+ propertyTable.push(new Element(element));
238
+ }
239
+ }
240
+ /////////////////////////////////////////////////////////////
241
+ // the rule warns when it finds:
242
+ // width being used with border, border-left, border-right, padding, padding-left, or padding-right
243
+ // height being used with border, border-top, border-bottom, padding, padding-top, or padding-bottom
244
+ // No error when box-sizing property is specified, as it assumes the user knows what he's doing.
245
+ // see https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size
246
+ /////////////////////////////////////////////////////////////
247
+ const boxModel = calculateBoxModel(propertyTable);
248
+ if (boxModel.width) {
249
+ let properties = [];
250
+ if (boxModel.right.value) {
251
+ properties = union(properties, boxModel.right.properties);
252
+ }
253
+ if (boxModel.left.value) {
254
+ properties = union(properties, boxModel.left.properties);
255
+ }
256
+ if (properties.length !== 0) {
257
+ for (const item of properties) {
258
+ this.addEntry(item.node, Rules.BewareOfBoxModelSize);
259
+ }
260
+ this.addEntry(boxModel.width.node, Rules.BewareOfBoxModelSize);
261
+ }
262
+ }
263
+ if (boxModel.height) {
264
+ let properties = [];
265
+ if (boxModel.top.value) {
266
+ properties = union(properties, boxModel.top.properties);
267
+ }
268
+ if (boxModel.bottom.value) {
269
+ properties = union(properties, boxModel.bottom.properties);
270
+ }
271
+ if (properties.length !== 0) {
272
+ for (const item of properties) {
273
+ this.addEntry(item.node, Rules.BewareOfBoxModelSize);
274
+ }
275
+ this.addEntry(boxModel.height.node, Rules.BewareOfBoxModelSize);
276
+ }
277
+ }
278
+ /////////////////////////////////////////////////////////////
279
+ // Properties ignored due to display
280
+ /////////////////////////////////////////////////////////////
281
+ // With 'display: inline-block', 'float' has no effect
282
+ let displayElems = this.fetchWithValue(propertyTable, 'display', 'inline-block');
283
+ if (displayElems.length > 0) {
284
+ const elem = this.fetch(propertyTable, 'float');
285
+ for (let index = 0; index < elem.length; index++) {
286
+ const node = elem[index].node;
287
+ const value = node.getValue();
288
+ if (value && !value.matches('none')) {
289
+ this.addEntry(node, Rules.PropertyIgnoredDueToDisplay, localize('rule.propertyIgnoredDueToDisplayInlineBlock', "inline-block is ignored due to the float. If 'float' has a value other than 'none', the box is floated and 'display' is treated as 'block'"));
290
+ }
291
+ }
292
+ }
293
+ // With 'display: block', 'vertical-align' has no effect
294
+ displayElems = this.fetchWithValue(propertyTable, 'display', 'block');
295
+ if (displayElems.length > 0) {
296
+ const elem = this.fetch(propertyTable, 'vertical-align');
297
+ for (let index = 0; index < elem.length; index++) {
298
+ this.addEntry(elem[index].node, Rules.PropertyIgnoredDueToDisplay, localize('rule.propertyIgnoredDueToDisplayBlock', "Property is ignored due to the display. With 'display: block', vertical-align should not be used."));
299
+ }
300
+ }
301
+ /////////////////////////////////////////////////////////////
302
+ // Avoid 'float'
303
+ /////////////////////////////////////////////////////////////
304
+ const elements = this.fetch(propertyTable, 'float');
305
+ for (let index = 0; index < elements.length; index++) {
306
+ const element = elements[index];
307
+ if (!this.isValidPropertyDeclaration(element)) {
308
+ this.addEntry(element.node, Rules.AvoidFloat);
309
+ }
310
+ }
311
+ /////////////////////////////////////////////////////////////
312
+ // Don't use duplicate declarations.
313
+ /////////////////////////////////////////////////////////////
314
+ for (let i = 0; i < propertyTable.length; i++) {
315
+ const element = propertyTable[i];
316
+ if (element.fullPropertyName !== 'background' && !this.validProperties[element.fullPropertyName]) {
317
+ const value = element.node.getValue();
318
+ if (value && this.documentText.charAt(value.offset) !== '-') {
319
+ const elements = this.fetch(propertyTable, element.fullPropertyName);
320
+ if (elements.length > 1) {
321
+ for (let k = 0; k < elements.length; k++) {
322
+ const value = elements[k].node.getValue();
323
+ if (value && this.documentText.charAt(value.offset) !== '-' && elements[k] !== element) {
324
+ this.addEntry(element.node, Rules.DuplicateDeclarations);
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ /////////////////////////////////////////////////////////////
332
+ // Unknown propery & When using a vendor-prefixed gradient, make sure to use them all.
333
+ /////////////////////////////////////////////////////////////
334
+ const isExportBlock = node.getSelectors().matches(":export");
335
+ if (!isExportBlock) {
336
+ const propertiesBySuffix = new NodesByRootMap();
337
+ let containsUnknowns = false;
338
+ for (const element of propertyTable) {
339
+ const decl = element.node;
340
+ if (this.isCSSDeclaration(decl)) {
341
+ let name = element.fullPropertyName;
342
+ const firstChar = name.charAt(0);
343
+ if (firstChar === '-') {
344
+ if (name.charAt(1) !== '-') { // avoid css variables
345
+ if (!this.cssDataManager.isKnownProperty(name) && !this.validProperties[name]) {
346
+ this.addEntry(decl.getProperty(), Rules.UnknownVendorSpecificProperty);
347
+ }
348
+ const nonPrefixedName = decl.getNonPrefixedPropertyName();
349
+ propertiesBySuffix.add(nonPrefixedName, name, decl.getProperty());
350
+ }
351
+ }
352
+ else {
353
+ const fullName = name;
354
+ if (firstChar === '*' || firstChar === '_') {
355
+ this.addEntry(decl.getProperty(), Rules.IEStarHack);
356
+ name = name.substr(1);
357
+ }
358
+ // _property and *property might be contributed via custom data
359
+ if (!this.cssDataManager.isKnownProperty(fullName) && !this.cssDataManager.isKnownProperty(name)) {
360
+ if (!this.validProperties[name]) {
361
+ this.addEntry(decl.getProperty(), Rules.UnknownProperty, localize('property.unknownproperty.detailed', "Unknown property: '{0}'", decl.getFullPropertyName()));
362
+ }
363
+ }
364
+ propertiesBySuffix.add(name, name, null); // don't pass the node as we don't show errors on the standard
365
+ }
366
+ }
367
+ else {
368
+ containsUnknowns = true;
369
+ }
370
+ }
371
+ if (!containsUnknowns) { // don't perform this test if there are
372
+ for (const suffix in propertiesBySuffix.data) {
373
+ const entry = propertiesBySuffix.data[suffix];
374
+ const actual = entry.names;
375
+ const needsStandard = this.cssDataManager.isStandardProperty(suffix) && (actual.indexOf(suffix) === -1);
376
+ if (!needsStandard && actual.length === 1) {
377
+ continue; // only the non-vendor specific rule is used, that's fine, no warning
378
+ }
379
+ const expected = [];
380
+ for (let i = 0, len = LintVisitor.prefixes.length; i < len; i++) {
381
+ const prefix = LintVisitor.prefixes[i];
382
+ if (this.cssDataManager.isStandardProperty(prefix + suffix)) {
383
+ expected.push(prefix + suffix);
384
+ }
385
+ }
386
+ const missingVendorSpecific = this.getMissingNames(expected, actual);
387
+ if (missingVendorSpecific || needsStandard) {
388
+ for (const node of entry.nodes) {
389
+ if (needsStandard) {
390
+ const message = localize('property.standard.missing', "Also define the standard property '{0}' for compatibility", suffix);
391
+ this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
392
+ }
393
+ if (missingVendorSpecific) {
394
+ const message = localize('property.vendorspecific.missing', "Always include all vendor specific properties: Missing: {0}", missingVendorSpecific);
395
+ this.addEntry(node, Rules.AllVendorPrefixes, message);
396
+ }
397
+ }
398
+ }
399
+ }
400
+ }
401
+ }
402
+ return true;
403
+ }
404
+ visitPrio(node) {
405
+ /////////////////////////////////////////////////////////////
406
+ // Don't use !important
407
+ /////////////////////////////////////////////////////////////
408
+ this.addEntry(node, Rules.AvoidImportant);
409
+ return true;
410
+ }
411
+ visitNumericValue(node) {
412
+ /////////////////////////////////////////////////////////////
413
+ // 0 has no following unit
414
+ /////////////////////////////////////////////////////////////
415
+ const funcDecl = node.findParent(nodes.NodeType.Function);
416
+ if (funcDecl && funcDecl.getName() === 'calc') {
417
+ return true;
418
+ }
419
+ const decl = node.findParent(nodes.NodeType.Declaration);
420
+ if (decl) {
421
+ const declValue = decl.getValue();
422
+ if (declValue) {
423
+ const value = node.getValue();
424
+ if (!value.unit || languageFacts.units.length.indexOf(value.unit.toLowerCase()) === -1) {
425
+ return true;
426
+ }
427
+ if (parseFloat(value.value) === 0.0 && !!value.unit && !this.validProperties[decl.getFullPropertyName()]) {
428
+ this.addEntry(node, Rules.ZeroWithUnit);
429
+ }
430
+ }
431
+ }
432
+ return true;
433
+ }
434
+ visitFontFace(node) {
435
+ const declarations = node.getDeclarations();
436
+ if (!declarations) {
437
+ // syntax error
438
+ return false;
439
+ }
440
+ let definesSrc = false, definesFontFamily = false;
441
+ let containsUnknowns = false;
442
+ for (const node of declarations.getChildren()) {
443
+ if (this.isCSSDeclaration(node)) {
444
+ const name = node.getProperty().getName().toLowerCase();
445
+ if (name === 'src') {
446
+ definesSrc = true;
447
+ }
448
+ if (name === 'font-family') {
449
+ definesFontFamily = true;
450
+ }
451
+ }
452
+ else {
453
+ containsUnknowns = true;
454
+ }
455
+ }
456
+ if (!containsUnknowns && (!definesSrc || !definesFontFamily)) {
457
+ this.addEntry(node, Rules.RequiredPropertiesForFontFace);
458
+ }
459
+ return true;
460
+ }
461
+ isCSSDeclaration(node) {
462
+ if (node instanceof nodes.Declaration) {
463
+ if (!node.getValue()) {
464
+ return false;
465
+ }
466
+ const property = node.getProperty();
467
+ if (!property) {
468
+ return false;
469
+ }
470
+ const identifier = property.getIdentifier();
471
+ if (!identifier || identifier.containsInterpolation()) {
472
+ return false;
473
+ }
474
+ return true;
475
+ }
476
+ return false;
477
+ }
478
+ visitHexColorValue(node) {
479
+ // Rule: #eeff0011 or #eeff00 or #ef01 or #ef0
480
+ const length = node.length;
481
+ if (length !== 9 && length !== 7 && length !== 5 && length !== 4) {
482
+ this.addEntry(node, Rules.HexColorLength);
483
+ }
484
+ return false;
485
+ }
486
+ visitFunction(node) {
487
+ const fnName = node.getName().toLowerCase();
488
+ let expectedAttrCount = -1;
489
+ let actualAttrCount = 0;
490
+ switch (fnName) {
491
+ case 'rgb(':
492
+ case 'hsl(':
493
+ expectedAttrCount = 3;
494
+ break;
495
+ case 'rgba(':
496
+ case 'hsla(':
497
+ expectedAttrCount = 4;
498
+ break;
499
+ }
500
+ if (expectedAttrCount !== -1) {
501
+ node.getArguments().accept(n => {
502
+ if (n instanceof nodes.BinaryExpression) {
503
+ actualAttrCount += 1;
504
+ return false;
505
+ }
506
+ return true;
507
+ });
508
+ if (actualAttrCount !== expectedAttrCount) {
509
+ this.addEntry(node, Rules.ArgsInColorFunction);
510
+ }
511
+ }
512
+ return true;
513
+ }
514
+ }
515
+ LintVisitor.prefixes = [
516
+ '-ms-', '-moz-', '-o-', '-webkit-', // Quite common
517
+ // '-xv-', '-atsc-', '-wap-', '-khtml-', 'mso-', 'prince-', '-ah-', '-hp-', '-ro-', '-rim-', '-tc-' // Quite un-common
518
+ ];