feat(stylelint-copyright): autofix, stricter config (#6374)

* feat(stylelint-copyright): autofix, stricter config

* revert TS

* oops
This commit is contained in:
Joshua Chen 2022-01-16 11:34:10 +08:00 committed by GitHub
parent 284c6166e7
commit 14bec7cf11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 202 additions and 176 deletions

View file

@ -12,6 +12,7 @@ packages/lqip-loader/lib/
packages/docusaurus/lib/
packages/docusaurus-*/lib/*
packages/docusaurus-*/lib-next/
packages/stylelint-copyright/lib/
copyUntypedFiles.mjs
packages/create-docusaurus/lib/*

1
.gitignore vendored
View file

@ -23,6 +23,7 @@ packages/create-docusaurus/lib/
packages/lqip-loader/lib/
packages/docusaurus/lib/
packages/docusaurus-*/lib/*
packages/stylelint-copyright/lib/
packages/docusaurus-*/lib-next/
website/netlifyDeployPreview/*

View file

@ -10,6 +10,7 @@ packages/docusaurus-*/lib/*
packages/docusaurus-*/lib-next/
packages/create-docusaurus/lib/*
packages/create-docusaurus/templates/*/docusaurus.config.js
packages/stylelint-copyright/lib/
__fixtures__
website/i18n

View file

@ -9,7 +9,16 @@ module.exports = {
extends: ['stylelint-config-recommended', 'stylelint-config-prettier'],
plugins: ['stylelint-copyright'],
rules: {
'docusaurus/copyright-header': true,
'docusaurus/copyright-header': [
true,
{
header: `*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.`,
},
],
'selector-pseudo-class-no-unknown': [
true,
{

View file

@ -31,7 +31,6 @@ export default {
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
setupFiles: ['./jest/stylelint-rule-test.js'],
moduleNameMapper: {
// Jest can't resolve CSS or asset imports
'^.+\\.(css|jpg|jpeg|png|svg)$': '<rootDir>/jest/emptyModule.js',

View file

@ -1,120 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// eslint-disable-next-line import/no-extraneous-dependencies
const stylelint = require('stylelint');
function getOutputCss(output) {
const result = output.results[0]._postcssResult;
return result.root.toString(result.opts.syntax);
}
global.testStylelintRule = (config, tests) => {
describe(tests.ruleName, () => {
const checkTestCaseContent = (testCase) =>
testCase.description || testCase.code || 'no description';
if (tests.accept && tests.accept.length) {
describe('accept cases', () => {
tests.accept.forEach((testCase) => {
const spec = testCase.only ? it.only : it;
spec(checkTestCaseContent(testCase), () => {
const options = {
code: testCase.code,
config,
syntax: tests.syntax,
};
return stylelint.lint(options).then((output) => {
expect(output.results[0].warnings).toEqual([]);
if (!tests.fix) {
return null;
}
// Check the fix.
return stylelint
.lint({...options, fix: true})
.then((fixedOutput) => getOutputCss(fixedOutput))
.then((fixedCode) => expect(fixedCode).toBe(testCase.fixed));
});
});
});
});
}
if (tests.reject && tests.reject.length) {
describe('reject cases', () => {
tests.reject.forEach((testCase) => {
const skip = testCase.skip ? it.skip : it;
const spec = testCase.only ? it.only : skip;
spec(checkTestCaseContent(testCase), () => {
const options = {
code: testCase.code,
config,
syntax: tests.syntax,
};
return stylelint.lint(options).then((output) => {
const {warnings} = output.results[0];
const warning = warnings[0];
expect(warnings.length).toBeGreaterThanOrEqual(1);
expect(testCase).toHaveMessage();
if (testCase.message != null) {
expect(warning.text).toBe(testCase.message);
}
if (testCase.line != null) {
expect(warning.line).toBe(testCase.line);
}
if (testCase.column != null) {
expect(warning.column).toBe(testCase.column);
}
if (!tests.fix) {
return null;
}
if (!testCase.fixed) {
throw new Error(
'If using { fix: true } in test tests, all reject cases must have { fixed: .. }',
);
}
// Check the fix.
return stylelint
.lint({...options, fix: true})
.then((fixedOutput) => getOutputCss(fixedOutput))
.then((fixedCode) => expect(fixedCode).toBe(testCase.fixed));
});
});
});
});
}
expect.extend({
toHaveMessage(testCase) {
if (testCase.message == null) {
return {
message: () =>
'Expected "reject" test case to have a "message" property',
pass: false,
};
}
return {
pass: true,
};
},
});
});
};

View file

@ -79,6 +79,7 @@
"@types/react-test-renderer": "^17.0.1",
"@types/semver": "^7.1.0",
"@types/shelljs": "^0.8.6",
"@types/stylelint": "^13.13.3",
"@types/wait-on": "^5.2.0",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",

View file

@ -1,3 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.skipToContent {
position: fixed;
top: 1rem;

View file

@ -16,7 +16,7 @@ Stylelint plugin to check CSS files for a copyright header.
{
"plugins": ["stylelint-copyright"],
"rules": {
"docusaurus/copyright-header": true
"docusaurus/copyright-header": [true, {"header": "\n * Copyright"}]
}
}
```

View file

@ -5,17 +5,128 @@
* LICENSE file in the root directory of this source tree.
*/
const stylelint = require('stylelint');
const path = require('path');
const rule = require('..');
const {ruleName, messages} = rule;
function getOutputCss(output) {
const result = output.results[0]._postcssResult;
return result.root.toString(result.opts.syntax);
}
function testStylelintRule(config, tests) {
describe(tests.ruleName, () => {
const checkTestCaseContent = (testCase) =>
testCase.description || testCase.code || 'no description';
if (tests.accept && tests.accept.length) {
describe('accept cases', () => {
tests.accept.forEach((testCase) => {
const spec = testCase.only ? it.only : it;
spec(checkTestCaseContent(testCase), () => {
const options = {
code: testCase.code,
config,
syntax: tests.syntax,
};
return stylelint.lint(options).then((output) => {
expect(output.results[0].warnings).toEqual([]);
if (!tests.fix) {
return null;
}
// Check the fix.
return stylelint
.lint({...options, fix: true})
.then((fixedOutput) => getOutputCss(fixedOutput))
.then((fixedCode) => expect(fixedCode).toBe(testCase.fixed));
});
});
});
});
}
if (tests.reject && tests.reject.length) {
describe('reject cases', () => {
tests.reject.forEach((testCase) => {
const skip = testCase.skip ? it.skip : it;
const spec = testCase.only ? it.only : skip;
spec(checkTestCaseContent(testCase), () => {
const options = {
code: testCase.code,
config,
syntax: tests.syntax,
};
return stylelint.lint(options).then((output) => {
const {warnings} = output.results[0];
const warning = warnings[0];
expect(warnings.length).toBeGreaterThanOrEqual(1);
expect(testCase).toHaveMessage();
if (testCase.message != null) {
expect(warning.text).toBe(testCase.message);
}
if (testCase.line != null) {
expect(warning.line).toBe(testCase.line);
}
if (testCase.column != null) {
expect(warning.column).toBe(testCase.column);
}
if (!tests.fix) {
return null;
}
if (!testCase.fixed) {
throw new Error(
'If using { fix: true } in test tests, all reject cases must have { fixed: .. }',
);
}
// Check the fix.
return stylelint
.lint({...options, fix: true})
.then((fixedOutput) => getOutputCss(fixedOutput))
.then((fixedCode) => expect(fixedCode).toBe(testCase.fixed));
});
});
});
});
}
expect.extend({
toHaveMessage(testCase) {
if (testCase.message == null) {
return {
message: () =>
'Expected "reject" test case to have a "message" property',
pass: false,
};
}
return {
pass: true,
};
},
});
});
}
testStylelintRule(
{
// Relative to repo root.
plugins: [path.join(process.cwd(), 'packages', 'stylelint-copyright')],
plugins: [path.join(__dirname, '..')],
rules: {
[ruleName]: true,
[ruleName]: [true, {header: '*\n * Copyright'}],
},
},
{
@ -28,27 +139,30 @@ testStylelintRule(
* Copyright
*/
.foo {}`,
},
{
code: `
/**
* copyright
*/
.foo {}`,
},
],
reject: [
{
code: `
/**
* copyright
*/
.foo {}`,
message: messages.rejected,
line: 1,
column: 1,
},
{
code: `
/**
* Copyleft
*/
.foo {}`,
message: messages.rejected,
line: 2,
line: 1,
column: 1,
},
{
@ -62,7 +176,7 @@ testStylelintRule(
*/
.foo {}`,
message: messages.rejected,
line: 2,
line: 1,
column: 1,
},
],

View file

@ -12,45 +12,50 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
rejected: 'Missing copyright in the header comment',
});
module.exports = stylelint.createPlugin(
const plugin = stylelint.createPlugin(
ruleName,
(actual) => (root, result) => {
const validOptions = stylelint.utils.validateOptions(result, ruleName, {
actual,
});
(primaryOption, secondaryOption, context) => (root, result) => {
const validOptions = stylelint.utils.validateOptions(
result,
ruleName,
{
actual: primaryOption,
possible: [true, false],
},
{
actual: secondaryOption,
possible: (v) => typeof v.header === 'string',
},
);
if (!validOptions) {
return;
}
root.walkComments((comment) => {
// Ignore root comments with copyright text.
if (
comment === comment.parent.first &&
/[Cc]opyright/.test(comment.text)
) {
if (
root.first &&
root.first.type === 'comment' &&
root.first.source.start.column === 1
) {
const {text} = root.first;
if (text === secondaryOption.header) {
return;
}
}
if (context.fix) {
root.first?.before(`/*${secondaryOption.header}\n */`);
return;
}
// Ignore non-root comments.
if (comment.type !== 'root' && comment !== comment.parent.first) {
return;
}
// Ignore indented comments.
if (comment.source.start.column > 1) {
return;
}
stylelint.utils.report({
message: messages.rejected,
node: comment,
result,
ruleName,
});
stylelint.utils.report({
message: messages.rejected,
node: root,
result,
ruleName,
});
},
);
module.exports = plugin;
module.exports.ruleName = ruleName;
module.exports.messages = messages;

View file

@ -4048,6 +4048,14 @@
resolved "https://registry.yarnpkg.com/@types/stringify-object/-/stringify-object-3.3.1.tgz#9ee394931e63468de0412a8e19c9f021a7d1d24d"
integrity sha512-bpCBW0O+QrMLNFBY/+rkZtGzcYRmc2aTD8qYHOMNUmednqETfEZtFcGEA11l9xqbIeiT1PgXG0eq3zqayVzZSQ==
"@types/stylelint@^13.13.3":
version "13.13.3"
resolved "https://registry.yarnpkg.com/@types/stylelint/-/stylelint-13.13.3.tgz#29ba9b7179e5632b12853252da191443607d32fc"
integrity sha512-xvYwobi9L69FXbJTimKYRNHyMwtmcJxMd1woI3U822rkW/f7wcZ6fsV1DqYPT+sNaO0qUtngiBhTQfMeItUvUA==
dependencies:
globby "11.x.x"
postcss "7.x.x"
"@types/supports-color@^8.1.1":
version "8.1.1"
resolved "https://registry.yarnpkg.com/@types/supports-color/-/supports-color-8.1.1.tgz#1b44b1b096479273adf7f93c75fc4ecc40a61ee4"
@ -9392,6 +9400,18 @@ globals@^13.6.0, globals@^13.9.0:
dependencies:
type-fest "^0.20.2"
globby@11.x.x, globby@^11.0.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
dependencies:
array-union "^2.1.0"
dir-glob "^3.0.1"
fast-glob "^3.2.9"
ignore "^5.2.0"
merge2 "^1.4.1"
slash "^3.0.0"
globby@^10.0.1:
version "10.0.2"
resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543"
@ -9406,18 +9426,6 @@ globby@^10.0.1:
merge2 "^1.2.3"
slash "^3.0.0"
globby@^11.0.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
dependencies:
array-union "^2.1.0"
dir-glob "^3.0.1"
fast-glob "^3.2.9"
ignore "^5.2.0"
merge2 "^1.4.1"
slash "^3.0.0"
globby@^12.0.2:
version "12.0.2"
resolved "https://registry.yarnpkg.com/globby/-/globby-12.0.2.tgz#53788b2adf235602ed4cabfea5c70a1139e1ab11"
@ -14832,7 +14840,7 @@ postcss-zindex@^5.0.1:
resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.0.1.tgz#c585724beb69d356af8c7e68847b28d6298ece03"
integrity sha512-nwgtJJys+XmmSGoYCcgkf/VczP8Mp/0OfSv3v0+fw0uABY4yxw+eFs0Xp9nAZHIKnS5j+e9ywQ+RD+ONyvl5pA==
"postcss@5 - 7", postcss@^7.0.14, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.39, postcss@^7.0.6:
"postcss@5 - 7", postcss@7.x.x, postcss@^7.0.14, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.39, postcss@^7.0.6:
version "7.0.39"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309"
integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==