first commit
This commit is contained in:
+868
@@ -0,0 +1,868 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce the use of baseline features.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Imports
|
||||
//-----------------------------------------------------------------------------
|
||||
import { BASELINE_HIGH, BASELINE_LOW, properties, propertyValues, atRules, mediaConditions, functions, units, selectors, } from "../data/baseline-data.js";
|
||||
import { namedColors } from "../data/colors.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { CSSRuleDefinition } from "../types.js"
|
||||
* @import { Identifier, FunctionNodePlain, Dimension } from "@eslint/css-tree"
|
||||
* @typedef {"notBaselineProperty" | "notBaselinePropertyValue" | "notBaselineAtRule" | "notBaselineFunction" | "notBaselineMediaCondition" | "notBaselineSelector" | "notBaselineUnit"} UseBaselineMessageIds
|
||||
* @typedef {[{
|
||||
* available?: "widely" | "newly" | number,
|
||||
* allowAtRules?: string[],
|
||||
* allowFunctions?: string[],
|
||||
* allowMediaConditions?: string[],
|
||||
* allowProperties?: string[],
|
||||
* allowPropertyValues?: { [property: string]: string[] },
|
||||
* allowSelectors?: string[],
|
||||
* allowUnits?: string[]
|
||||
* }]} UseBaselineOptions
|
||||
* @typedef {CSSRuleDefinition<{ RuleOptions: UseBaselineOptions, MessageIds: UseBaselineMessageIds }>} UseBaselineRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Represents a property that is supported via `@supports`.
|
||||
*/
|
||||
class SupportedProperty {
|
||||
/**
|
||||
* The name of the property.
|
||||
* @type {string}
|
||||
*/
|
||||
name;
|
||||
/**
|
||||
* Supported identifier values.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
#identifiers = new Set();
|
||||
/**
|
||||
* Supported units.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
#units = new Set();
|
||||
/**
|
||||
* Supported function types.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
#functions = new Set();
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} name The name of the property.
|
||||
*/
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
/**
|
||||
* Adds an identifier to the list of supported identifiers.
|
||||
* @param {string} identifier The identifier to add.
|
||||
* @returns {void}
|
||||
*/
|
||||
addIdentifier(identifier) {
|
||||
this.#identifiers.add(identifier);
|
||||
}
|
||||
/**
|
||||
* Determines if an identifier is supported.
|
||||
* @param {string} identifier The identifier to check.
|
||||
* @returns {boolean} `true` if the identifier is supported, `false` if not.
|
||||
*/
|
||||
hasIdentifier(identifier) {
|
||||
return this.#identifiers.has(identifier);
|
||||
}
|
||||
/**
|
||||
* Determines if any identifiers are supported.
|
||||
* @returns {boolean} `true` if any identifiers are supported, `false` if not.
|
||||
*/
|
||||
hasIdentifiers() {
|
||||
return this.#identifiers.size > 0;
|
||||
}
|
||||
/**
|
||||
* Adds a unit to the list of supported units.
|
||||
* @param {string} unit The unit to add.
|
||||
* @returns {void}
|
||||
*/
|
||||
addUnit(unit) {
|
||||
this.#units.add(unit);
|
||||
}
|
||||
/**
|
||||
* Determines if a unit is supported.
|
||||
* @param {string} unit The unit to check.
|
||||
* @returns {boolean} `true` if the unit is supported, `false` if not.
|
||||
*/
|
||||
hasUnit(unit) {
|
||||
return this.#units.has(unit);
|
||||
}
|
||||
/**
|
||||
* Determines if any units are supported.
|
||||
* @returns {boolean} `true` if any units are supported, `false` if not.
|
||||
*/
|
||||
hasUnits() {
|
||||
return this.#units.size > 0;
|
||||
}
|
||||
/**
|
||||
* Adds a function to the list of supported functions.
|
||||
* @param {string} func The function to add.
|
||||
* @returns {void}
|
||||
*/
|
||||
addFunction(func) {
|
||||
this.#functions.add(func);
|
||||
}
|
||||
/**
|
||||
* Determines if a function is supported.
|
||||
* @param {string} func The function to check.
|
||||
* @returns {boolean} `true` if the function is supported, `false` if not.
|
||||
*/
|
||||
hasFunction(func) {
|
||||
return this.#functions.has(func);
|
||||
}
|
||||
/**
|
||||
* Determines if any functions are supported.
|
||||
* @returns {boolean} `true` if any functions are supported, `false` if not.
|
||||
*/
|
||||
hasFunctions() {
|
||||
return this.#functions.size > 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents an `@supports` rule and everything it enables.
|
||||
*/
|
||||
class SupportsRule {
|
||||
/**
|
||||
* The properties supported by this rule.
|
||||
* @type {Map<string, SupportedProperty>}
|
||||
*/
|
||||
#properties = new Map();
|
||||
/**
|
||||
* The selectors supported by this rule.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
#selectors = new Set();
|
||||
/**
|
||||
* Adds a property to the rule.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {SupportedProperty} The supported property object.
|
||||
*/
|
||||
addProperty(property) {
|
||||
if (this.#properties.has(property)) {
|
||||
return this.#properties.get(property);
|
||||
}
|
||||
const supportedProperty = new SupportedProperty(property);
|
||||
this.#properties.set(property, supportedProperty);
|
||||
return supportedProperty;
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports a property.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {boolean} `true` if the property is supported, `false` if not.
|
||||
*/
|
||||
hasProperty(property) {
|
||||
return this.#properties.has(property);
|
||||
}
|
||||
/**
|
||||
* Gets the supported property.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {SupportedProperty} The supported property.
|
||||
*/
|
||||
getProperty(property) {
|
||||
return this.#properties.get(property);
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports a property value.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {string} identifier The identifier to check.
|
||||
* @returns {boolean} `true` if the property value is supported, `false` if not.
|
||||
*/
|
||||
hasPropertyIdentifier(property, identifier) {
|
||||
const supportedProperty = this.#properties.get(property);
|
||||
if (!supportedProperty) {
|
||||
return false;
|
||||
}
|
||||
return supportedProperty.hasIdentifier(identifier);
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports any property values.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {boolean} `true` if any property values are supported, `false` if not.
|
||||
*/
|
||||
hasPropertyIdentifiers(property) {
|
||||
const supportedProperty = this.#properties.get(property);
|
||||
if (!supportedProperty) {
|
||||
return false;
|
||||
}
|
||||
return supportedProperty.hasIdentifiers();
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports a function.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {string} func The function to check.
|
||||
* @returns {boolean} `true` if the function is supported, `false` if not.
|
||||
*/
|
||||
hasFunction(property, func) {
|
||||
const supportedProperty = this.#properties.get(property);
|
||||
if (!supportedProperty) {
|
||||
return false;
|
||||
}
|
||||
return supportedProperty.hasFunction(func);
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports any functions.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {boolean} `true` if any functions are supported, `false` if not.
|
||||
*/
|
||||
hasFunctions(property) {
|
||||
const supportedProperty = this.#properties.get(property);
|
||||
if (!supportedProperty) {
|
||||
return false;
|
||||
}
|
||||
return supportedProperty.hasFunctions();
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports a unit.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {string} unit The unit to check.
|
||||
* @returns {boolean} `true` if the unit is supported, `false` if not.
|
||||
*/
|
||||
hasPropertyUnit(property, unit) {
|
||||
const supportedProperty = this.#properties.get(property);
|
||||
if (!supportedProperty) {
|
||||
return false;
|
||||
}
|
||||
return supportedProperty.hasUnit(unit);
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports any units.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {boolean} `true` if any units are supported, `false` if not.
|
||||
*/
|
||||
hasPropertyUnits(property) {
|
||||
const supportedProperty = this.#properties.get(property);
|
||||
if (!supportedProperty) {
|
||||
return false;
|
||||
}
|
||||
return supportedProperty.hasUnits();
|
||||
}
|
||||
/**
|
||||
* Adds a selector to the rule.
|
||||
* @param {string} selector The name of the selector.
|
||||
* @returns {void}
|
||||
*/
|
||||
addSelector(selector) {
|
||||
this.#selectors.add(selector);
|
||||
}
|
||||
/**
|
||||
* Determines if the rule supports a selector.
|
||||
* @param {string} selector The name of the selector.
|
||||
* @returns {boolean} `true` if the selector is supported, `false` if not.
|
||||
*/
|
||||
hasSelector(selector) {
|
||||
return this.#selectors.has(selector);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents a collection of supports rules.
|
||||
*/
|
||||
class SupportsRules {
|
||||
/**
|
||||
* A collection of supports rules.
|
||||
* @type {Array<SupportsRule>}
|
||||
*/
|
||||
#rules = [];
|
||||
/**
|
||||
* Adds a rule to the collection.
|
||||
* @param {SupportsRule} rule The rule to add.
|
||||
* @returns {void}
|
||||
*/
|
||||
push(rule) {
|
||||
this.#rules.push(rule);
|
||||
}
|
||||
/**
|
||||
* Removes the last rule from the collection.
|
||||
* @returns {SupportsRule} The last rule in the collection.
|
||||
*/
|
||||
pop() {
|
||||
return this.#rules.pop();
|
||||
}
|
||||
/**
|
||||
* Retrieves the last rule in the collection.
|
||||
* @returns {SupportsRule} The last rule in the collection.
|
||||
*/
|
||||
last() {
|
||||
return this.#rules.at(-1);
|
||||
}
|
||||
/**
|
||||
* Determines if any rule supports a property.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {boolean} `true` if any rule supports the property, `false` if not.
|
||||
*/
|
||||
hasProperty(property) {
|
||||
return this.#rules.some(rule => rule.hasProperty(property));
|
||||
}
|
||||
/**
|
||||
* Determines if any rule supports a property identifier.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {string} identifier The identifier to check.
|
||||
* @returns {boolean} `true` if any rule supports the property value, `false` if not.
|
||||
*/
|
||||
hasPropertyIdentifier(property, identifier) {
|
||||
return this.#rules.some(rule => rule.hasPropertyIdentifier(property, identifier));
|
||||
}
|
||||
/**
|
||||
* Determines if any rule supports any property identifiers.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {boolean} `true` if any rule supports the property values, `false` if not.
|
||||
*/
|
||||
hasPropertyIdentifiers(property) {
|
||||
return this.#rules.some(rule => rule.hasPropertyIdentifiers(property));
|
||||
}
|
||||
/**
|
||||
* Determines if any rule supports a function.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {string} func The function to check.
|
||||
* @returns {boolean} `true` if any rule supports the function, `false` if not.
|
||||
*/
|
||||
hasPropertyFunction(property, func) {
|
||||
return this.#rules.some(rule => rule.hasFunction(property, func));
|
||||
}
|
||||
/**
|
||||
* Determines if any rule supports any functions.
|
||||
* @param {string} property The name of the property.
|
||||
* @returns {boolean} `true` if any rule supports the functions, `false` if not.
|
||||
*/
|
||||
hasPropertyFunctions(property) {
|
||||
return this.#rules.some(rule => rule.hasFunctions(property));
|
||||
}
|
||||
/**
|
||||
* Determines if any rule supports a unit.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {string} unit The unit to check.
|
||||
* @returns {boolean} `true` if any rule supports the unit, `false` if not.
|
||||
*/
|
||||
hasPropertyUnit(property, unit) {
|
||||
return this.#rules.some(rule => rule.hasPropertyUnit(property, unit));
|
||||
}
|
||||
/**
|
||||
* Determines if any rule supports a selector.
|
||||
* @param {string} selector The name of the selector.
|
||||
* @returns {boolean} `true` if any rule supports the selector, `false` if not.
|
||||
*/
|
||||
hasSelector(selector) {
|
||||
return this.#rules.some(rule => rule.hasSelector(selector));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents the required availability of a feature.
|
||||
*/
|
||||
class BaselineAvailability {
|
||||
/**
|
||||
* The preferred Baseline year.
|
||||
* @type {number}
|
||||
*/
|
||||
#baselineYear = undefined;
|
||||
/**
|
||||
* The preferred Baseline status.
|
||||
* @type {number}
|
||||
*/
|
||||
#baselineStatus = undefined;
|
||||
/**
|
||||
* @param {string | number} availability The required level of feature availability.
|
||||
*/
|
||||
constructor(availability) {
|
||||
this.availability = availability;
|
||||
if (typeof availability === "number") {
|
||||
this.#baselineYear = availability;
|
||||
}
|
||||
else {
|
||||
this.#baselineStatus =
|
||||
availability === "widely" ? BASELINE_HIGH : BASELINE_LOW;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determines whether a feature meets the required availability.
|
||||
* @param {Object} encodedStatus A feature's encoded baseline status and year.
|
||||
* @returns {boolean} `true` if the feature is supported, `false` if not.
|
||||
*/
|
||||
isSupported(encodedStatus) {
|
||||
if (!encodedStatus) {
|
||||
// if we don't know the status, assume it's supported
|
||||
return true;
|
||||
}
|
||||
const parts = encodedStatus.split(":");
|
||||
const status = Number(parts[0]);
|
||||
const year = Number(parts[1] || NaN);
|
||||
if (this.#baselineYear) {
|
||||
return year <= this.#baselineYear;
|
||||
}
|
||||
return status >= this.#baselineStatus;
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
export default /** @satisfies {UseBaselineRuleDefinition} */ ({
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description: "Enforce the use of baseline features",
|
||||
recommended: true,
|
||||
url: "https://github.com/eslint/css/blob/main/docs/rules/use-baseline.md",
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
available: {
|
||||
anyOf: [
|
||||
{
|
||||
enum: ["widely", "newly"],
|
||||
},
|
||||
{
|
||||
// baseline year
|
||||
type: "integer",
|
||||
minimum: 2000,
|
||||
maximum: new Date().getFullYear(),
|
||||
},
|
||||
],
|
||||
},
|
||||
allowAtRules: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(atRules.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
allowFunctions: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(functions.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
allowMediaConditions: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(mediaConditions.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
allowProperties: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(properties.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
allowPropertyValues: {
|
||||
type: "object",
|
||||
properties: Object.fromEntries(Array.from(propertyValues.entries()).map(([prop, valuesMap]) => [
|
||||
prop,
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(valuesMap.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
])),
|
||||
additionalProperties: false,
|
||||
},
|
||||
allowSelectors: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(selectors.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
allowUnits: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(units.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
defaultOptions: [
|
||||
{
|
||||
available: "widely",
|
||||
allowAtRules: [],
|
||||
allowFunctions: [],
|
||||
allowMediaConditions: [],
|
||||
allowProperties: [],
|
||||
allowPropertyValues: {},
|
||||
allowSelectors: [],
|
||||
allowUnits: [],
|
||||
},
|
||||
],
|
||||
messages: {
|
||||
notBaselineProperty: "Property '{{property}}' is not a {{availability}} available baseline feature.",
|
||||
notBaselinePropertyValue: "Value '{{value}}' of property '{{property}}' is not a {{availability}} available baseline feature.",
|
||||
notBaselineAtRule: "At-rule '@{{atRule}}' is not a {{availability}} available baseline feature.",
|
||||
notBaselineFunction: "Function '{{function}}' is not a {{availability}} available baseline feature.",
|
||||
notBaselineMediaCondition: "Media condition '{{condition}}' is not a {{availability}} available baseline feature.",
|
||||
notBaselineSelector: "Selector '{{selector}}' is not a {{availability}} available baseline feature.",
|
||||
notBaselineUnit: "Unit '{{unit}}' is not a {{availability}} available baseline feature.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const baselineAvailability = new BaselineAvailability(context.options[0].available);
|
||||
const supportsRules = new SupportsRules();
|
||||
const allowAtRules = new Set(context.options[0].allowAtRules);
|
||||
const allowProperties = new Set(context.options[0].allowProperties);
|
||||
const allowSelectors = new Set(context.options[0].allowSelectors);
|
||||
const allowFunctions = new Set(context.options[0].allowFunctions);
|
||||
const allowMediaConditions = new Set(context.options[0].allowMediaConditions);
|
||||
const allowUnits = new Set(context.options[0].allowUnits);
|
||||
const allowPropertyValuesMap = new Map();
|
||||
for (const [prop, values] of Object.entries(context.options[0].allowPropertyValues)) {
|
||||
allowPropertyValuesMap.set(prop, new Set(values));
|
||||
}
|
||||
/**
|
||||
* Checks a property value identifier to see if it's a baseline feature.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {Identifier} child The node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkPropertyValueIdentifier(property, child) {
|
||||
// named colors are always valid
|
||||
if (namedColors.has(child.name)) {
|
||||
return;
|
||||
}
|
||||
const allowedValues = allowPropertyValuesMap.get(property);
|
||||
if (allowedValues?.has(child.name)) {
|
||||
return;
|
||||
}
|
||||
const possiblePropertyValues = propertyValues.get(property);
|
||||
// if we don't know of any possible property values, just skip it
|
||||
if (!possiblePropertyValues) {
|
||||
return;
|
||||
}
|
||||
const featureStatus = possiblePropertyValues.get(child.name);
|
||||
// if we don't know of any possible property values, just skip it
|
||||
if (featureStatus === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!baselineAvailability.isSupported(featureStatus)) {
|
||||
context.report({
|
||||
loc: child.loc,
|
||||
messageId: "notBaselinePropertyValue",
|
||||
data: {
|
||||
property,
|
||||
value: child.name,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks a property value function to see if it's a baseline feature.
|
||||
* @param {FunctionNodePlain} child The node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkPropertyValueFunction(child) {
|
||||
if (allowFunctions.has(child.name)) {
|
||||
return;
|
||||
}
|
||||
const featureStatus = functions.get(child.name);
|
||||
// if we don't know of any possible property values, just skip it
|
||||
if (featureStatus === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!baselineAvailability.isSupported(featureStatus)) {
|
||||
context.report({
|
||||
loc: child.loc,
|
||||
messageId: "notBaselineFunction",
|
||||
data: {
|
||||
function: child.name,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks a property value unit to see if it's a baseline feature.
|
||||
* @param {string} property The name of the property.
|
||||
* @param {Dimension} child The node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkPropertyValueUnit(property, child) {
|
||||
if (allowUnits.has(child.unit)) {
|
||||
return;
|
||||
}
|
||||
const featureStatus = units.get(child.unit);
|
||||
// if we don't know of this unit, just skip it
|
||||
if (featureStatus === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!baselineAvailability.isSupported(featureStatus)) {
|
||||
context.report({
|
||||
loc: child.loc,
|
||||
messageId: "notBaselineUnit",
|
||||
data: {
|
||||
unit: child.unit,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
"Atrule[name=/^supports$/i]"() {
|
||||
supportsRules.push(new SupportsRule());
|
||||
},
|
||||
"Atrule[name=/^supports$/i] > AtrulePrelude > Condition"(node) {
|
||||
const supportsRule = supportsRules.last();
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
const conditionChild = node.children[i];
|
||||
// if a SupportsDeclaration is preceded by "not" then we don't consider it
|
||||
if (conditionChild.type === "Identifier" &&
|
||||
conditionChild.name === "not") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// save the supported properties and values for this at-rule
|
||||
if (conditionChild.type === "SupportsDeclaration") {
|
||||
const { declaration } = conditionChild;
|
||||
const property = declaration.property;
|
||||
const supportedProperty = supportsRule.addProperty(property);
|
||||
declaration.value.children.forEach(child => {
|
||||
if (child.type === "Identifier") {
|
||||
supportedProperty.addIdentifier(child.name);
|
||||
return;
|
||||
}
|
||||
if (child.type === "Dimension") {
|
||||
supportedProperty.addUnit(child.unit);
|
||||
return;
|
||||
}
|
||||
if (child.type === "Function") {
|
||||
supportedProperty.addFunction(child.name);
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (conditionChild.type === "FeatureFunction" &&
|
||||
conditionChild.feature === "selector") {
|
||||
for (const selectorChild of conditionChild.value
|
||||
.children) {
|
||||
supportsRule.addSelector(selectorChild.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rule > Block > Declaration"(node) {
|
||||
const property = node.property;
|
||||
// ignore unknown properties - no-invalid-properties already catches this
|
||||
if (!properties.has(property)) {
|
||||
return;
|
||||
}
|
||||
if (allowProperties.has(property)) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* Step 1: Check that the property is in the baseline.
|
||||
*
|
||||
* If the property has been tested in a @supports rule, we don't need to
|
||||
* check it because it won't be applied if the browser doesn't support it.
|
||||
*/
|
||||
if (!supportsRules.hasProperty(property)) {
|
||||
const featureStatus = properties.get(property);
|
||||
if (!baselineAvailability.isSupported(featureStatus)) {
|
||||
context.report({
|
||||
loc: {
|
||||
start: node.loc.start,
|
||||
end: {
|
||||
line: node.loc.start.line,
|
||||
column: node.loc.start.column +
|
||||
node.property.length,
|
||||
},
|
||||
},
|
||||
messageId: "notBaselineProperty",
|
||||
data: {
|
||||
property,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
/*
|
||||
* If the property isn't in baseline, then we don't go
|
||||
* on to check the values. If the property itself isn't
|
||||
* in baseline then chances are the values aren't too,
|
||||
* and there's no need to report multiple errors for the
|
||||
* same property.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* With tolerant parsing, it's possible that the value is `Raw`
|
||||
* and therefore doesn't have children. If that's the case then
|
||||
* we just exit.
|
||||
*/
|
||||
if (!node.value?.children) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* Step 2: Check that the property values are in the baseline.
|
||||
*/
|
||||
for (const child of node.value.children) {
|
||||
if (child.type === "Identifier") {
|
||||
// if the property value has been tested in a @supports rule, don't check it
|
||||
if (!supportsRules.hasPropertyIdentifier(property, child.name)) {
|
||||
checkPropertyValueIdentifier(property, child);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (child.type === "Dimension") {
|
||||
if (!supportsRules.hasPropertyUnit(property, child.unit)) {
|
||||
checkPropertyValueUnit(property, child);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (child.type === "Function") {
|
||||
if (!supportsRules.hasPropertyFunction(property, child.name)) {
|
||||
checkPropertyValueFunction(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Atrule[name=/^supports$/i]:exit"() {
|
||||
supportsRules.pop();
|
||||
},
|
||||
"Atrule[name=/^media$/i] > AtrulePrelude > MediaQueryList > MediaQuery > Condition"(node) {
|
||||
for (const child of node.children) {
|
||||
// ignore unknown media conditions - no-invalid-at-rules already catches this
|
||||
if (!mediaConditions.has(child.name)) {
|
||||
continue;
|
||||
}
|
||||
if (child.type !== "Feature") {
|
||||
continue;
|
||||
}
|
||||
if (allowMediaConditions.has(child.name)) {
|
||||
continue;
|
||||
}
|
||||
const featureStatus = mediaConditions.get(child.name);
|
||||
if (!baselineAvailability.isSupported(featureStatus)) {
|
||||
const loc = child.loc;
|
||||
context.report({
|
||||
loc: {
|
||||
start: {
|
||||
line: loc.start.line,
|
||||
// add 1 to account for the @ symbol
|
||||
column: loc.start.column + 1,
|
||||
},
|
||||
end: {
|
||||
line: loc.start.line,
|
||||
column:
|
||||
// add 1 to account for the @ symbol
|
||||
loc.start.column +
|
||||
child.name.length +
|
||||
1,
|
||||
},
|
||||
},
|
||||
messageId: "notBaselineMediaCondition",
|
||||
data: {
|
||||
condition: child.name,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
Atrule(node) {
|
||||
// ignore unknown at-rules - no-invalid-at-rules already catches this
|
||||
const atRuleName = node.name.toLowerCase();
|
||||
if (!atRules.has(atRuleName)) {
|
||||
return;
|
||||
}
|
||||
if (allowAtRules.has(atRuleName)) {
|
||||
return;
|
||||
}
|
||||
const featureStatus = atRules.get(atRuleName);
|
||||
if (!baselineAvailability.isSupported(featureStatus)) {
|
||||
const loc = node.loc;
|
||||
context.report({
|
||||
loc: {
|
||||
start: loc.start,
|
||||
end: {
|
||||
line: loc.start.line,
|
||||
// add 1 to account for the @ symbol
|
||||
column: loc.start.column + node.name.length + 1,
|
||||
},
|
||||
},
|
||||
messageId: "notBaselineAtRule",
|
||||
data: {
|
||||
atRule: node.name,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
"PseudoClassSelector,PseudoElementSelector"(node) {
|
||||
const selector = node.name;
|
||||
if (!selectors.has(selector)) {
|
||||
return;
|
||||
}
|
||||
if (allowSelectors.has(selector)) {
|
||||
return;
|
||||
}
|
||||
// if the selector has been tested in a @supports rule, don't check it
|
||||
if (supportsRules.hasSelector(selector)) {
|
||||
return;
|
||||
}
|
||||
const featureStatus = selectors.get(selector);
|
||||
if (!baselineAvailability.isSupported(featureStatus)) {
|
||||
const loc = node.loc;
|
||||
// some selectors are prefixed with the : or :: symbols
|
||||
let prefixSymbolLength = 0;
|
||||
if (node.type.startsWith("PseudoClass")) {
|
||||
prefixSymbolLength = 1;
|
||||
}
|
||||
else if (node.type.startsWith("PseudoElement")) {
|
||||
prefixSymbolLength = 2;
|
||||
}
|
||||
context.report({
|
||||
loc: {
|
||||
start: loc.start,
|
||||
end: {
|
||||
line: loc.start.line,
|
||||
column: loc.start.column +
|
||||
selector.length +
|
||||
prefixSymbolLength,
|
||||
},
|
||||
},
|
||||
messageId: "notBaselineSelector",
|
||||
data: {
|
||||
selector,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
NestingSelector(node) {
|
||||
// NestingSelector implies CSS nesting
|
||||
const selector = "nesting";
|
||||
if (allowSelectors.has(selector)) {
|
||||
return;
|
||||
}
|
||||
const featureStatus = selectors.get(selector);
|
||||
if (baselineAvailability.isSupported(featureStatus)) {
|
||||
return;
|
||||
}
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "notBaselineSelector",
|
||||
data: {
|
||||
selector,
|
||||
availability: baselineAvailability.availability,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user