mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-02 00:01:01 +02:00
109 lines
3.2 KiB
JavaScript
109 lines
3.2 KiB
JavaScript
/* Copyright 2025 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { parse, registerWalkers, Root } from "postcss-values-parser";
|
|
import { isString } from "stylelint/lib/utils/validateTypes.mjs";
|
|
import stylelint from "stylelint";
|
|
|
|
const {
|
|
createPlugin,
|
|
utils: { report, validateOptions },
|
|
} = stylelint;
|
|
|
|
registerWalkers(Root);
|
|
|
|
const ruleName = "pdfjs/no-unused-custom-properties";
|
|
|
|
// It's a very basic linter: we don't take into account scopes.
|
|
// But it should be enough for our use case.
|
|
|
|
/** @type {import('stylelint').Plugin} */
|
|
const ruleFunction =
|
|
(enabled, { ignoreList = [] } = {}, context = {}) =>
|
|
(root, result) => {
|
|
const validOptions = validateOptions(
|
|
result,
|
|
ruleName,
|
|
{
|
|
actual: enabled,
|
|
possible: [true],
|
|
},
|
|
{
|
|
actual: ignoreList,
|
|
possible: [isString],
|
|
optional: true,
|
|
}
|
|
);
|
|
|
|
if (!validOptions) {
|
|
return;
|
|
}
|
|
|
|
ignoreList = ignoreList.map(s => (s.startsWith("--") ? s : `--${s}`));
|
|
|
|
const usedCustomProperties = new Set(ignoreList);
|
|
const definedCustomProperties = new Set();
|
|
const usedBy = new Map();
|
|
root.walkDecls(decl => {
|
|
let definingProperty = null;
|
|
if (decl.prop.startsWith("--")) {
|
|
// This is a custom property definition.
|
|
definingProperty = decl.prop;
|
|
definedCustomProperties.add(definingProperty);
|
|
}
|
|
// Parse the declaration value to find var() usages.
|
|
const parsedValue = parse(decl.value);
|
|
parsedValue.walkFuncs(node => {
|
|
if (!node.isVar || node.nodes.length === 0) {
|
|
return;
|
|
}
|
|
// This is a var() function; get the custom property name.
|
|
const property = node.nodes[0].value;
|
|
if (!definingProperty) {
|
|
// This is a usage of a custom property but not in a definition.
|
|
// width: var(--foo);
|
|
usedCustomProperties.add(property);
|
|
return;
|
|
}
|
|
let usages = usedBy.get(property);
|
|
if (!usages) {
|
|
usages = [];
|
|
usedBy.set(property, usages);
|
|
}
|
|
// Record that this custom property is used by the defining property.
|
|
// --foo: var(--bar);
|
|
// bar is really used only if foo is.
|
|
usages.push(definingProperty);
|
|
});
|
|
});
|
|
const isUsed = p =>
|
|
usedCustomProperties.has(p) || (usedBy.get(p) || []).some(isUsed);
|
|
for (const customProperty of definedCustomProperties) {
|
|
if (isUsed(customProperty)) {
|
|
continue;
|
|
}
|
|
report({
|
|
message: `Custom property "${customProperty}" is defined but never used.`,
|
|
node: root,
|
|
result,
|
|
ruleName,
|
|
});
|
|
}
|
|
};
|
|
|
|
ruleFunction.ruleName = ruleName;
|
|
|
|
export default createPlugin(ruleName, ruleFunction);
|