diff --git a/.eslintignore b/.eslintignore index 85470d48eb..a82d6bdc1b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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/* diff --git a/.gitignore b/.gitignore index 8e116a2e94..04d22349cf 100644 --- a/.gitignore +++ b/.gitignore @@ -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/* diff --git a/.prettierignore b/.prettierignore index 147e1b1ba5..3c33d06c76 100644 --- a/.prettierignore +++ b/.prettierignore @@ -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 diff --git a/.stylelintrc.js b/.stylelintrc.js index b0accebfe5..4340c05386 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -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, { diff --git a/jest.config.mjs b/jest.config.mjs index 377a0142cf..343d5b7ded 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -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)$': '/jest/emptyModule.js', diff --git a/jest/stylelint-rule-test.js b/jest/stylelint-rule-test.js deleted file mode 100644 index 9dcda69b05..0000000000 --- a/jest/stylelint-rule-test.js +++ /dev/null @@ -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, - }; - }, - }); - }); -}; diff --git a/package.json b/package.json index afefab589e..a1f41e4a5a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/docusaurus-theme-classic/src/theme/SkipToContent/styles.module.css b/packages/docusaurus-theme-classic/src/theme/SkipToContent/styles.module.css index 1ded61ed5c..47d72ff750 100644 --- a/packages/docusaurus-theme-classic/src/theme/SkipToContent/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/SkipToContent/styles.module.css @@ -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; diff --git a/packages/stylelint-copyright/README.md b/packages/stylelint-copyright/README.md index 9e3d9010fd..b67c9f17e3 100644 --- a/packages/stylelint-copyright/README.md +++ b/packages/stylelint-copyright/README.md @@ -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"}] } } ``` diff --git a/packages/stylelint-copyright/__tests__/index.test.js b/packages/stylelint-copyright/__tests__/index.test.js index 7dbd9cb09a..048e3753a9 100644 --- a/packages/stylelint-copyright/__tests__/index.test.js +++ b/packages/stylelint-copyright/__tests__/index.test.js @@ -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, }, ], diff --git a/packages/stylelint-copyright/index.js b/packages/stylelint-copyright/index.js index beb6c38eb4..adf34dd2fd 100644 --- a/packages/stylelint-copyright/index.js +++ b/packages/stylelint-copyright/index.js @@ -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; diff --git a/yarn.lock b/yarn.lock index 248d634dfb..af344ca40f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==