private visitRuleSet()

in src/services/lint.ts [268:470]


	private visitRuleSet(node: nodes.RuleSet): boolean {
		/////////////////////////////////////////////////////////////
		//	Lint - Don't use empty rulesets.
		/////////////////////////////////////////////////////////////
		const declarations = node.getDeclarations();
		if (!declarations) {
			// syntax error
			return false;
		}


		if (!declarations.hasChildren()) {
			this.addEntry(node.getSelectors(), Rules.EmptyRuleSet);
		}

		const propertyTable: Element[] = [];
		for (const element of declarations.getChildren()) {
			if (element instanceof nodes.Declaration) {
				propertyTable.push(new Element(element));
			}
		}

		/////////////////////////////////////////////////////////////
		// the rule warns when it finds:
		// width being used with border, border-left, border-right, padding, padding-left, or padding-right
		// height being used with border, border-top, border-bottom, padding, padding-top, or padding-bottom
		// No error when box-sizing property is specified, as it assumes the user knows what he's doing.
		// see https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size
		/////////////////////////////////////////////////////////////
		const boxModel = calculateBoxModel(propertyTable);
		if (boxModel.width) {
			let properties: Element[] = [];
			if (boxModel.right.value) {
				properties = union(properties, boxModel.right.properties);
			}
			if (boxModel.left.value) {
				properties = union(properties, boxModel.left.properties);
			}
			if (properties.length !== 0) {
				for (const item of properties) {
					this.addEntry(item.node, Rules.BewareOfBoxModelSize);
				}
				this.addEntry(boxModel.width.node, Rules.BewareOfBoxModelSize);
			}
		}
		if (boxModel.height) {
			let properties: Element[] = [];
			if (boxModel.top.value) {
				properties = union(properties, boxModel.top.properties);
			}
			if (boxModel.bottom.value) {
				properties = union(properties, boxModel.bottom.properties);
			}
			if (properties.length !== 0) {
				for (const item of properties) {
					this.addEntry(item.node, Rules.BewareOfBoxModelSize);
				}
				this.addEntry(boxModel.height.node, Rules.BewareOfBoxModelSize);
			}
		}

		/////////////////////////////////////////////////////////////
		//	Properties ignored due to display
		/////////////////////////////////////////////////////////////

		// With 'display: inline-block', 'float' has no effect
		let displayElems = this.fetchWithValue(propertyTable, 'display', 'inline-block');
		if (displayElems.length > 0) {
			const elem = this.fetch(propertyTable, 'float');
			for (let index = 0; index < elem.length; index++) {
				const node = elem[index].node;
				const value = node.getValue();
				if (value && !value.matches('none')) {
					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'"));
				}
			}
		}

		// With 'display: block', 'vertical-align' has no effect
		displayElems = this.fetchWithValue(propertyTable, 'display', 'block');
		if (displayElems.length > 0) {
			const elem = this.fetch(propertyTable, 'vertical-align');
			for (let index = 0; index < elem.length; index++) {
				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."));
			}
		}

		/////////////////////////////////////////////////////////////
		//	Avoid 'float'
		/////////////////////////////////////////////////////////////

		const elements: Element[] = this.fetch(propertyTable, 'float');
		for (let index = 0; index < elements.length; index++) {
			const element = elements[index];
			if (!this.isValidPropertyDeclaration(element)) {
				this.addEntry(element.node, Rules.AvoidFloat);
			}
		}

		/////////////////////////////////////////////////////////////
		//	Don't use duplicate declarations.
		/////////////////////////////////////////////////////////////
		for (let i = 0; i < propertyTable.length; i++) {
			const element = propertyTable[i];
			if (element.fullPropertyName !== 'background' && !this.validProperties[element.fullPropertyName]) {
				const value = element.node.getValue();
				if (value && this.documentText.charAt(value.offset) !== '-') {
					const elements = this.fetch(propertyTable, element.fullPropertyName);
					if (elements.length > 1) {
						for (let k = 0; k < elements.length; k++) {
							const value = elements[k].node.getValue();
							if (value && this.documentText.charAt(value.offset) !== '-' && elements[k] !== element) {
								this.addEntry(element.node, Rules.DuplicateDeclarations);
							}
						}
					}
				}
			}
		}

		/////////////////////////////////////////////////////////////
		//	Unknown propery & When using a vendor-prefixed gradient, make sure to use them all.
		/////////////////////////////////////////////////////////////

		const isExportBlock = node.getSelectors().matches(":export");

		if (!isExportBlock) {
			const propertiesBySuffix = new NodesByRootMap();
			let containsUnknowns = false;

			for (const element of propertyTable) {
				const decl = element.node;
				if (this.isCSSDeclaration(decl)) {
					let name = element.fullPropertyName;
					const firstChar = name.charAt(0);

					if (firstChar === '-') {
						if (name.charAt(1) !== '-') { // avoid css variables
							if (!this.cssDataManager.isKnownProperty(name) && !this.validProperties[name]) {
								this.addEntry(decl.getProperty()!, Rules.UnknownVendorSpecificProperty);
							}
							const nonPrefixedName = decl.getNonPrefixedPropertyName();
							propertiesBySuffix.add(nonPrefixedName, name, decl.getProperty());
						}
					} else {
						const fullName = name;
						if (firstChar === '*' || firstChar === '_') {
							this.addEntry(decl.getProperty()!, Rules.IEStarHack);
							name = name.substr(1);
						}

						// _property and *property might be contributed via custom data
						if (!this.cssDataManager.isKnownProperty(fullName) && !this.cssDataManager.isKnownProperty(name)) {
							if (!this.validProperties[name]) {
								this.addEntry(decl.getProperty()!, Rules.UnknownProperty, localize('property.unknownproperty.detailed', "Unknown property: '{0}'", decl.getFullPropertyName()));
							}
						}

						propertiesBySuffix.add(name, name, null); // don't pass the node as we don't show errors on the standard
					}
				} else {
					containsUnknowns = true;
				}
			}

			if (!containsUnknowns) { // don't perform this test if there are
				for (const suffix in propertiesBySuffix.data) {
					const entry = propertiesBySuffix.data[suffix];
					const actual = entry.names;

					const needsStandard = this.cssDataManager.isStandardProperty(suffix) && (actual.indexOf(suffix) === -1);
					if (!needsStandard && actual.length === 1) {
						continue; // only the non-vendor specific rule is used, that's fine, no warning
					}

					const expected: string[] = [];
					for (let i = 0, len = LintVisitor.prefixes.length; i < len; i++) {
						const prefix = LintVisitor.prefixes[i];
						if (this.cssDataManager.isStandardProperty(prefix + suffix)) {
							expected.push(prefix + suffix);
						}
					}

					const missingVendorSpecific = this.getMissingNames(expected, actual);
					if (missingVendorSpecific || needsStandard) {
						for (const node of entry.nodes) {
							if (needsStandard) {
								const message = localize('property.standard.missing', "Also define the standard property '{0}' for compatibility", suffix);
								this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
							}
							if (missingVendorSpecific) {
								const message = localize('property.vendorspecific.missing', "Always include all vendor specific properties: Missing: {0}", missingVendorSpecific);
								this.addEntry(node, Rules.AllVendorPrefixes, message);
							}
						}
					}
				}
			}
		}


		return true;
	}