Skip to content

Commit 4bf7594

Browse files
committed
Add a new linting task in order to detect unused/unknown css variables in the Firefox build
The goal is to be able to catch the errors before making a release. And fix some css issues (especially the missing css code for the newly added menu.css)
1 parent 36de2d9 commit 4bf7594

File tree

12 files changed

+1225
-26
lines changed

12 files changed

+1225
-26
lines changed

.github/workflows/lint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ jobs:
3232

3333
- name: Run lint-chromium
3434
run: npx gulp lint-chromium
35+
36+
- name: Run lint-mozcentral
37+
run: npx gulp lint-mozcentral

.stylelintrc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"float": ["inline-start", "inline-end"]
1818
},
1919
"length-zero-no-unit": [true, {
20-
ignore: ["custom-properties"]
20+
"ignore": ["custom-properties"]
2121
}],
2222
"selector-pseudo-element-colon-notation": "double",
23-
"shorthand-property-no-redundant-values": true,
24-
},
23+
"shorthand-property-no-redundant-values": true
24+
}
2525
}

external/builder/builder.mjs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,22 @@ function preprocess(inFilename, outFilename, defines) {
3939
}
4040

4141
function expandCssImports(content, baseUrl) {
42+
if (defines.GECKOVIEW) {
43+
// In Geckoview, we don't need some styles.
44+
const startComment = "/* Ignored in GECKOVIEW: begin */";
45+
const endComment = "/* Ignored in GECKOVIEW: end */";
46+
const beginIndex = content.indexOf(startComment);
47+
const endIndex = content.indexOf(endComment);
48+
if (beginIndex >= 0 && endIndex > beginIndex) {
49+
content =
50+
content.substring(0, beginIndex) +
51+
content.substring(endIndex + endComment.length);
52+
}
53+
}
54+
4255
return content.replaceAll(
4356
/^\s*@import\s+url\(([^)]+)\);\s*$/gm,
4457
function (all, url) {
45-
if (defines.GECKOVIEW) {
46-
switch (url) {
47-
case "annotation_editor_layer_builder.css":
48-
return "";
49-
}
50-
}
5158
const file = path.join(path.dirname(baseUrl), url);
5259
const imported = fs.readFileSync(file, "utf8").toString();
5360
return expandCssImports(imported, file);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* Copyright 2025 Mozilla Foundation
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
// TODO: Remove the exception below once someone figures out how to fix it.
17+
// eslint-disable-next-line import/no-unresolved
18+
import { parse, registerWalkers, Root } from "postcss-values-parser";
19+
import { isString } from "stylelint/lib/utils/validateTypes.mjs";
20+
import stylelint from "stylelint";
21+
22+
const {
23+
createPlugin,
24+
utils: { report, validateOptions },
25+
} = stylelint;
26+
27+
registerWalkers(Root);
28+
29+
const ruleName = "pdfjs/no-unused-custom-properties";
30+
31+
// It's a very basic linter: we don't take into account scopes.
32+
// But it should be enough for our use case.
33+
34+
/** @type {import('stylelint').Plugin} */
35+
const ruleFunction =
36+
(enabled, { ignoreList = [] } = {}, context = {}) =>
37+
(root, result) => {
38+
const validOptions = validateOptions(
39+
result,
40+
ruleName,
41+
{
42+
actual: enabled,
43+
possible: [true],
44+
},
45+
{
46+
actual: ignoreList,
47+
possible: [isString],
48+
optional: true,
49+
}
50+
);
51+
52+
if (!validOptions) {
53+
return;
54+
}
55+
56+
ignoreList = ignoreList.map(s => (s.startsWith("--") ? s : `--${s}`));
57+
58+
const usedCustomProperties = new Set(ignoreList);
59+
const definedCustomProperties = new Set();
60+
const usedBy = new Map();
61+
root.walkDecls(decl => {
62+
let definingProperty = null;
63+
if (decl.prop.startsWith("--")) {
64+
// This is a custom property definition.
65+
definingProperty = decl.prop;
66+
definedCustomProperties.add(definingProperty);
67+
}
68+
// Parse the declaration value to find var() usages.
69+
const parsedValue = parse(decl.value);
70+
parsedValue.walkFuncs(node => {
71+
if (!node.isVar || node.nodes.length === 0) {
72+
return;
73+
}
74+
// This is a var() function; get the custom property name.
75+
const property = node.nodes[0].value;
76+
if (!definingProperty) {
77+
// This is a usage of a custom property but not in a definition.
78+
// width: var(--foo);
79+
usedCustomProperties.add(property);
80+
return;
81+
}
82+
let usages = usedBy.get(property);
83+
if (!usages) {
84+
usages = [];
85+
usedBy.set(property, usages);
86+
}
87+
// Record that this custom property is used by the defining property.
88+
// --foo: var(--bar);
89+
// bar is really used only if foo is.
90+
usages.push(definingProperty);
91+
});
92+
});
93+
const isUsed = p =>
94+
usedCustomProperties.has(p) || (usedBy.get(p) || []).some(isUsed);
95+
for (const customProperty of definedCustomProperties) {
96+
if (isUsed(customProperty)) {
97+
continue;
98+
}
99+
report({
100+
message: `Custom property "${customProperty}" is defined but never used.`,
101+
node: root,
102+
result,
103+
ruleName,
104+
});
105+
}
106+
};
107+
108+
ruleFunction.ruleName = ruleName;
109+
110+
export default createPlugin(ruleName, ruleFunction);

gulpfile.mjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,35 @@ gulp.task("lint", function (done) {
20992099
});
21002100
});
21012101

2102+
gulp.task(
2103+
"lint-mozcentral",
2104+
gulp.series("mozcentral", function runLintMozcentral(done) {
2105+
console.log();
2106+
console.log("### Checking mozilla-central files");
2107+
2108+
const styleLintOptions = [
2109+
"../../node_modules/stylelint/bin/stylelint.mjs",
2110+
"**/*.css",
2111+
"--report-needless-disables",
2112+
"--config",
2113+
"../../stylelint-mozcentral.json",
2114+
];
2115+
2116+
const styleLintProcess = startNode(styleLintOptions, {
2117+
stdio: "inherit",
2118+
cwd: BUILD_DIR + "mozcentral/",
2119+
});
2120+
styleLintProcess.on("close", function (styleLintCode) {
2121+
if (styleLintCode !== 0) {
2122+
done(new Error("Stylelint failed."));
2123+
return;
2124+
}
2125+
console.log("files checked, no errors found");
2126+
done();
2127+
});
2128+
})
2129+
);
2130+
21022131
gulp.task(
21032132
"lint-chromium",
21042133
gulp.series(

0 commit comments

Comments
 (0)