diff --git a/.eslintrc.js b/.eslintrc.js index dcc83f2b5e..d5ab59e3de 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,7 +7,10 @@ module.exports = { // Enable dotfile linting '!.*', 'node_modules', - 'node_modules/.*' + 'node_modules/.*', + + // Prevent CHANGELOG history changes + 'CHANGELOG.md' ], overrides: [ { @@ -18,7 +21,13 @@ module.exports = { 'plugin:n/recommended', 'plugin:promise/recommended' ], - files: ['**/*.{cjs,js,mjs}'], + files: [ + '**/*.{cjs,js,mjs}', + + // Check markdown `*.md` contains valid code blocks + // https://github.com/eslint/eslint-plugin-markdown#advanced-configuration + '**/*.md/*.{cjs,js,mjs}' + ], parserOptions: { ecmaVersion: 'latest' }, @@ -98,6 +107,31 @@ module.exports = { env: { jest: true } + }, + { + // Add plugin for markdown `*.md` code blocks + extends: ['plugin:markdown/recommended'], + files: ['**/*.md'], + plugins: ['markdown'], + processor: 'markdown/markdown' + }, + { + files: ['**/coding-standards/js.md/*.{cjs,js,mjs}'], + env: { + browser: true + }, + rules: { + // Ignore unused example code + 'no-undef': 'off', + 'no-unused-vars': 'off', + + // Ignore paths to example modules + 'import/no-unresolved': 'off', + 'n/no-missing-import': 'off', + + // Allow `var` in example code + 'no-var': 'off' + } } ], parserOptions: { diff --git a/.lintstagedrc.js b/.lintstagedrc.js index 53c3dee85b..ff1e38e872 100644 --- a/.lintstagedrc.js +++ b/.lintstagedrc.js @@ -1,6 +1,6 @@ const { ESLint } = require('eslint') -module.exports = { +const commands = { // ESLint's configuration makes it ignore built files in `dist` or `packages/govuk-frontend/dist` // that we want left alone, as well as the polyfills. // The glob used by lint-staged to trigger the linting on commit isn't aware @@ -11,9 +11,16 @@ module.exports = { // as recommended by lint-staged. // // https://github.com/okonet/lint-staged#how-can-i-ignore-files-from-eslintignore - '*.{cjs,js,mjs}': filterTask('npm run lint:js:cli -- --fix'), - '*.{json,md,yaml,yml}': 'npm run lint:prettier:cli -- --write', - '*.scss': 'npm run lint:scss:cli -- --fix --allow-empty-input' + eslint: filterTask('npm run lint:js:cli -- --fix'), + prettier: 'npm run lint:prettier:cli -- --write', + stylelint: 'npm run lint:scss:cli -- --fix --allow-empty-input' +} + +module.exports = { + '*.{cjs,js,mjs}': commands.eslint, + '*.{json,yaml,yml}': commands.prettier, + '*.md': [commands.eslint, commands.stylelint, commands.prettier], + '*.scss': commands.stylelint } // Configure paths to ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 330e54455f..8476105665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,7 @@ const Button = require('govuk-frontend/dist/govuk/components/button/button') For example using `import`: -```js +```mjs import Button from 'govuk-frontend/dist/govuk-esm/components/button/button.mjs' ``` diff --git a/docs/contributing/coding-standards/css.md b/docs/contributing/coding-standards/css.md index c0d55114b4..50f42172ad 100644 --- a/docs/contributing/coding-standards/css.md +++ b/docs/contributing/coding-standards/css.md @@ -24,9 +24,9 @@ The naming convention follows this pattern: .block__element {} .block--modifier {} -.govuk-card // Block - the root of a component -.govuk-card__body // Element - a part of the block -.govuk-card--active // Modifier - a variant of the block +.govuk-card {} // Block - the root of a component +.govuk-card__body {} // Element - a part of the block +.govuk-card--active {} // Modifier - a variant of the block ``` It uses double hyphens (`--`) and underscores (`__`) so that the block, element @@ -35,9 +35,9 @@ or modifiers themselves can be hyphen delimited without causing ambiguity. For example: ```scss -.govuk-phase-banner -.govuk-phase-banner__phase-tag -.govuk-phase-banner__phase-tag--light-blue +.govuk-phase-banner {} +.govuk-phase-banner__phase-tag {} +.govuk-phase-banner__phase-tag--light-blue {} ``` ### Further reading: @@ -56,24 +56,24 @@ given class name. It also discourages excessive nesting. Bad: -``` +```scss .govuk-breadcrumb { - ... + // ... &__item { - ... + // ... } } ``` Good: -``` +```scss .govuk-breadcrumb { - ... + // ... } .govuk-breadcrumb__item { - ... + // ... } ``` @@ -132,13 +132,13 @@ We use the following rules when linting files: Bad: -``` +```scss .selector {padding: 0; border: 0;} ``` Good: -``` +```scss .selector { padding: 0; border: 0; @@ -149,7 +149,7 @@ Good: Bad: -``` +```scss .selector { color: #005ea5; } @@ -157,7 +157,7 @@ Bad: Good: -``` +```scss .selector { color: $govuk-blue; } @@ -167,13 +167,13 @@ Good: Bad: -``` +```scss $white: #FFF; ``` Good: -``` +```scss $white: #ffffff; ``` @@ -181,37 +181,17 @@ $white: #ffffff; Bad: -``` +```scss #content { - ... + // ... } ``` Good: -``` +```scss .govuk-wrapper { - ... -} -``` - -### Use single colons for pseudo-element selectors - -This is to ensure compatibility with Internet Explorer 8, which doesn't support the double colon syntax. - -Bad: - -``` -.selector::before { - content: "foo"; -} -``` - -Good: - -``` -.selector:before { - content: "foo"; + // ... } ``` @@ -219,31 +199,31 @@ Good: Bad: -``` +```scss p { margin: 0; em { - ... + // ... } } a { - ... + // ... } ``` Good: -``` +```scss p { margin: 0; em { - ... + // ... } } a { - ... + // ... } ``` @@ -251,24 +231,24 @@ a { Bad: -``` +```scss .govuk-breadcrumb { - ... + // ... &__item { - ... + // ... } } ``` Good: -``` +```scss .govuk-breadcrumb { - ... + // ... } .govuk-breadcrumb__item { - ... + // ... } ``` @@ -276,13 +256,13 @@ Good: Bad: -``` +```scss @extend %contain-floats; ``` Good: -``` +```scss @include clearfix; ``` @@ -290,13 +270,13 @@ Good: Bad: -``` +```scss margin: 1px 2px 3px 2px; ``` Good: -``` +```scss margin: 1px 2px 3px; ``` @@ -304,7 +284,7 @@ margin: 1px 2px 3px; Bad: -``` +```scss @import 'foo'; $govuk-font-family-gds-transport: 'GDS Transport', arial, sans-serif; @@ -316,7 +296,7 @@ $govuk-font-family-gds-transport: 'GDS Transport', arial, sans-serif; Good: -``` +```scss @import "foo"; $govuk-font-family-gds-transport: "GDS Transport", arial, sans-serif; @@ -332,14 +312,14 @@ $govuk-font-family-gds-transport: "GDS Transport", arial, sans-serif; Bad: -``` +```scss @import "_foo.scss"; @import "_bar/foo.scss"; ``` Good: -``` +```scss @import "foo"; @import "bar/foo"; ``` @@ -348,7 +328,7 @@ Good: Bad: -``` +```scss .foo { content:"bar"; } @@ -356,7 +336,7 @@ Bad: Good: -``` +```scss .foo { content: "bar"; } @@ -366,7 +346,7 @@ Good: Bad: -``` +```scss @if ($foo == $bar) { $baz: 1; } @@ -374,7 +354,7 @@ Bad: Good: -``` +```scss @if $foo == $bar { $baz: 1; } @@ -384,7 +364,7 @@ Good: Bad: -``` +```scss @if $foo == null { $baz: 1; } @@ -392,7 +372,7 @@ Bad: Good: -``` +```scss @if not $foo { $baz: 1; } @@ -402,15 +382,15 @@ Good: Bad: -``` -.selector { +```scss +.selector-1 { margin: 5px+15px; } $foo: 1; $bar: 3; -.selector { +.selector-2 { margin: $foo+$bar+"px"; } @@ -428,15 +408,15 @@ $bar: 2-1; Good: -``` -.selector { +```scss +.selector-1 { margin: 5px + 15px; } $foo: 1; $bar: 3; -.selector { +.selector-2 { margin: $foo + $bar + "px"; } @@ -456,7 +436,7 @@ $bar: 2 - 1; Bad: -``` +```scss @mixin FONT_STACK() { font-family: $govuk-font-stack; } @@ -464,7 +444,7 @@ Bad: Good: -``` +```scss @mixin font-stack() { font-family: $govuk-font-stack; } @@ -474,7 +454,7 @@ Good: Bad: -``` +```scss .selector { margin: 0px; } @@ -482,7 +462,7 @@ Bad: Good: -``` +```scss .selector { margin: 0; } @@ -492,7 +472,7 @@ Good: Bad: -``` +```scss .selector { margin: 0 } @@ -502,7 +482,7 @@ $my-example-var: value Good: -``` +```scss .selector { margin: 0; } @@ -514,7 +494,7 @@ $my-example-var: value; Bad: -``` +```scss .selector { font-size: 0.50em; } @@ -522,7 +502,7 @@ Bad: Good: -``` +```scss .selector { font-size: .5em; } diff --git a/docs/contributing/coding-standards/js.md b/docs/contributing/coding-standards/js.md index ff8cbb2e2b..9f70f8b0e5 100644 --- a/docs/contributing/coding-standards/js.md +++ b/docs/contributing/coding-standards/js.md @@ -4,7 +4,7 @@ JavaScript files have the same name as the component's folder name. Test files have a `.test` suffix placed before the file extension. -``` +```console component ├── component.mjs └── component.test.js @@ -12,7 +12,7 @@ component ## Skeleton -```js +```mjs /** * Component name * @@ -39,6 +39,7 @@ Example.prototype.init = function () { } // Code goes here + var $module = this.$module } export default Example @@ -64,7 +65,7 @@ class="govuk-js-header-toggle" Use `/** ... */` for multi-line comments. Include a description, and specify types and values for all parameters and return values. -```js +```mjs /** * Get the first descendent (child) of an HTML element that matches a given tag name * @@ -72,7 +73,7 @@ Use `/** ... */` for multi-line comments. Include a description, and specify typ * @param {string} tagName - Tag name (for example 'div') * @returns {Element} Ancestor element */ -function ($element, tagName) { +function exampleHelper ($element, tagName) { // Code goes here return $element.querySelector(tagName) } @@ -90,7 +91,7 @@ Use the prototype design pattern to structure your code. Create a constructor and define any variables that the object needs. -```js +```mjs function Example ($module) { // Code goes here } @@ -98,7 +99,7 @@ function Example ($module) { Assign methods to the prototype object. Do not overwrite the prototype with a new object as this makes inheritance impossible. -```js +```mjs // Bad Example.prototype = { init: function () { @@ -114,19 +115,19 @@ Example.prototype.init = function () { When initialising an object, use the `new` keyword. -```js +```mjs // Bad -var myExample = Example() +var myExample1 = Example() // Good -var myExample = new Example() +var myExample2 = new Example() ``` ## Modules Use ECMAScript (ES) modules (`import`/`export`) over CommonJS and other formats. You can always transpile to your preferred module system. -```js +```mjs import { closestAttributeValue } from '../common/index.mjs' // Code goes here diff --git a/docs/contributing/coding-standards/nunjucks-api.md b/docs/contributing/coding-standards/nunjucks-api.md index 1e078d88d1..6ba3116d5d 100644 --- a/docs/contributing/coding-standards/nunjucks-api.md +++ b/docs/contributing/coding-standards/nunjucks-api.md @@ -19,13 +19,19 @@ When providing _content_ to a macro, say for a label or a button, we accept two Example: -`{{ govukButton({"text": "Button text"}) }}` +```njk +{{ govukButton({ text: "Button text" }) }} +``` -`{{ govukButton({"html": "Button text"}) }}` +```njk +{{ govukButton({ html: "Button text" }) }} +``` Example of implementing logic in a component template: -`{{ params.html | safe if params.html else params.text }}` +```njk +{{ params.html | safe if params.html else params.text }} +``` Example shows that if `html` and `text` options are present, then `html` takes precedence over `text` and we are not escaping it. @@ -37,25 +43,25 @@ If a component depends on another component, we group the options for the depend Example of a component depending on another component -``` +```njk {{ govukLabel({ - "text": "Label text", - "errorMessage": { - "text": "Error message" + text: "Label text", + errorMessage: { + text: "Error message" } }) }} ``` Example of a component depending on two other components -``` +```njk {{ govukInput({ - "name": "example-input", - "label": { - "text": "Label text" + name: "example-input", + label: { + text: "Label text" }, - "errorMessage": { - "text": "Error message" + errorMessage: { + text: "Error message" } }) }} ``` @@ -66,9 +72,13 @@ When there is a need to specify html attributes, such as _checked, disabled, id, Example: -`{{ govukButton({"disabled": true}) }}` +```njk +{{ govukButton({ disabled: true }) }} +``` -`{{ govukCheckbox({"checked": true}) }}` +```njk +{{ govukCheckbox({ checked: true }) }} +``` ## Defining additional HTML attributes @@ -78,10 +88,10 @@ You cannot use this to set attributes that are already defined, such as class Example: -``` +```njk {{ govukButton({ - "attributes" : { - "data-target" : "contact-by-text", + attributes: { + "data-target": "contact-by-text", "aria-labelledby": "error-summary-heading-example-1", "tabindex": "-1" } @@ -94,16 +104,16 @@ When a component accepts a _single array of items_ for an output, such as checkb Example: -``` +```njk {{ govukCheckbox({ - "items": [ + items: [ { - "value": "checkbox value", - "text": "Checkbox text" + value: "checkbox value", + text: "Checkbox text" }, { - "value": "checkbox value 2", - "text": "Checkbox text 2" + value: "checkbox value 2", + text: "Checkbox text 2" } ] }) }} @@ -115,17 +125,17 @@ When a component has multiple visual presentations, such default button vs start Default button example: -``` +```njk {{ govukButton({ - "text" : "Continue" + text: "Continue" }) }} ``` Start button example: -``` +```njk {{ govukButton({ - "text" : "Start", - "classes" : "govuk-button--start" + text: "Start", + classes: "govuk-button--start" }) }} ``` diff --git a/docs/contributing/managing-change.md b/docs/contributing/managing-change.md index 735da414f9..cf8df7967a 100644 --- a/docs/contributing/managing-change.md +++ b/docs/contributing/managing-change.md @@ -81,8 +81,9 @@ Mixins cannot be invoked within functions, so we use the `_should-warn` and `_wa /// See https://github.com/alphagov/govuk-frontend/issues/1234 @function govuk-double($number) { @if _should-warn("double") { - @warn _warning-message("double", "govuk-double($number) is deprecated. Use govuk-multiply($number, 2) instead.") + @warn _warning-message("double", "govuk-double($number) is deprecated. Use govuk-multiply($number, 2) instead."); } + @return govuk-multiply($number, 2); } ``` @@ -102,7 +103,7 @@ If possible, update the mixin or function to maintain the existing functionality /// @param {Number} $angle Angle to reticulate by /// @param {Boolean} $rightAngle Deprecated. Use $angle: 90 instead. @mixin govuk-reticulate-splines($spline, $angle: 180, $rightAngle: false) { - @if ($rightAngle != false) { + @if $rightAngle != false { @include _warning("right-angle", "Passing $rightAngle to govuk-reticulate-splines is deprecated. Pass $angle: 90 instead."); $angle: 90; @@ -117,7 +118,7 @@ If possible, update the mixin or function to maintain the existing functionality ```scss // @deprecated .govuk-foo-old-class-name { - foo: bar; + content: "foo"; } ``` @@ -167,7 +168,7 @@ Add 'Deprecated.' to the description for the parameter. /// @param {String} $spline Spline to reticulate /// @param {String} $spilne Deprecated. Use $spline instead. @function govuk-reticulate-splines($spline, $spilne: false) { - @if ($spilne != false) { + @if $spilne != false { @include _warning("spilne", "Passing $spilne to govuk-reticulate-splines is deprecated. Pass $spline instead."); $spline: $spilne; @@ -185,7 +186,7 @@ Keep the old name in the selector list, and mark it as deprecated. // govuk-old-class-name is deprecated. Use govuk-new-class-name instead. .govuk-old-class-name, .govuk-new-class-name { - foo: bar; + content: "foo"; } ``` diff --git a/docs/contributing/running-locally.md b/docs/contributing/running-locally.md index 2d50158a1b..5dd0070195 100644 --- a/docs/contributing/running-locally.md +++ b/docs/contributing/running-locally.md @@ -11,7 +11,7 @@ If you're an external contributor make sure to [fork this project first](https:/ ## 2. Clone repository -``` +```shell git clone git@github.com:alphagov/govuk-frontend.git # or clone your own fork cd govuk-frontend @@ -30,7 +30,7 @@ To enable this we use [nvm (Node Version Manager)](https://github.com/creationix We use [npm](https://docs.npmjs.com/getting-started/what-is-npm) to manage the dependencies in development. -``` +```shell npm install ``` @@ -38,7 +38,7 @@ npm install This will build sources, serve pages and watch for changes. -``` +```shell npm start ``` diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index ce77e7aed0..4071c48797 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -135,7 +135,7 @@ Where `` is the name of the component you've changed. To make sure your changes work in the Design System, use `npm link` to test before publishing, as follows: -```bash +```shell cd ../govuk-design-system git checkout main git pull @@ -145,7 +145,7 @@ npm link ../govuk-frontend/packages/govuk-frontend/ When you've finished testing, run this command to unlink the package: -```bash +```shell npm unlink ../govuk-frontend/packages/govuk-frontend/ ``` diff --git a/docs/releasing/publishing-from-a-support-branch.md b/docs/releasing/publishing-from-a-support-branch.md index 3c88db587f..b1ba0cf9ae 100644 --- a/docs/releasing/publishing-from-a-support-branch.md +++ b/docs/releasing/publishing-from-a-support-branch.md @@ -63,7 +63,7 @@ Read the docs for [what to do before publishing a release](/docs/releasing/befor 6. Apply the new version number by running: - ``` + ```shell npm version --no-git-tag-version --workspace govuk-frontend ``` diff --git a/docs/releasing/publishing.md b/docs/releasing/publishing.md index 0f2b91c71a..d5100ac510 100644 --- a/docs/releasing/publishing.md +++ b/docs/releasing/publishing.md @@ -22,7 +22,7 @@ Developers should pair on releases. When remote working, it can be useful to be 6. Apply the new version number by running: - ``` + ```shell npm version --no-git-tag-version --workspace govuk-frontend ``` @@ -32,7 +32,7 @@ Developers should pair on releases. When remote working, it can be useful to be 7. Update browser data from ["Can I use"](https://caniuse.com) by running: - ``` + ```shell npx update-browserslist-db@latest ``` diff --git a/docs/releasing/testing-and-linting.md b/docs/releasing/testing-and-linting.md index d18a33e683..ec6bf38a7a 100644 --- a/docs/releasing/testing-and-linting.md +++ b/docs/releasing/testing-and-linting.md @@ -20,7 +20,7 @@ Visual regression tests help us check for any unintended visual changes to our c To test the whole codebase, run: -``` +```shell npm test ``` @@ -42,7 +42,7 @@ Note: There's a watch mode that keeps a testing session open waiting for changes To lint the whole codebase, run: -``` +```shell npm run lint ``` @@ -52,7 +52,7 @@ See [Tasks](../contributing/tasks.md) for details of what `npm run lint` does. ### Running only Sass linting -``` +```shell npm run lint:scss ``` @@ -60,7 +60,7 @@ See [CSS Coding Standards](/docs/contributing/coding-standards/css.md#linting) f ### Running only JavaScript linting -``` +```shell npm run lint:js ``` @@ -78,7 +78,7 @@ If a component uses JavaScript, we also write functional tests in a `[component If you want to inspect a test that's running in the browser, configure Jest Puppeteer in non-headless mode with the environment variable `HEADLESS=false` and then use [Jest Puppeteer's debug mode](https://github.com/argos-ci/jest-puppeteer/blob/main/README.md#debug-mode) to pause the test execution. -``` +```shell HEADLESS=false npx jest --watch src/govuk/components/tag/accessibility.test.mjs ``` @@ -102,7 +102,9 @@ For components, the snapshots are stored in `[component-name directory]/_snapsho If a snapshot test fails, review the difference in the console. If the change is the correct change to make, run: -`npm test -- -u packages/govuk-frontend/src/govuk/components/button` +```shell +npm test -- -u packages/govuk-frontend/src/govuk/components/button +``` This will update the snapshot file. Commit this file separately with a commit message that explains you're updating the snapshot file and an explanation of what caused the change. @@ -118,7 +120,7 @@ When you run the tests locally (for example, using `npm run test:screenshots --w When Github Actions is running against a PR from a fork, the Percy secret is not available and Percy does not generate any screenshots. Other tests will continue to run as normal. You will see the following messages in the output: -``` +```console [percy] Skipping visual tests [percy] Error: Missing Percy token ``` diff --git a/jest.config.mjs b/jest.config.mjs index b0a584414d..9a0fb23c19 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -53,7 +53,7 @@ const config = { * * @type {import('@jest/types').Config.InitialOptions} * @example - * ```console + * ```shell * npx jest --selectProjects "Nunjucks macro tests" * npx jest --selectProjects "JavaScript unit tests" * ``` diff --git a/package-lock.json b/package-lock.json index 06fe177eb5..e7f5b32407 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "eslint-plugin-es-x": "^7.1.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsdoc": "^46.2.4", + "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-n": "^16.0.0", "eslint-plugin-promise": "^6.1.1", "govuk-frontend-config": "*", @@ -40,6 +41,8 @@ "jest-puppeteer": "^9.0.0", "jest-serializer-html": "^7.1.0", "lint-staged": "^13.2.2", + "postcss-markdown": "^1.2.0", + "postcss-scss": "^4.0.6", "prettier": "^2.8.8", "standard": "^17.1.0", "stylelint": "^14.16.1", @@ -4659,6 +4662,15 @@ "integrity": "sha512-YcZe50jhltsCq7rc9MNZC/4QB/OnA2Pd6hrOSTOFajtabN+38slqgDDCeE/0F83SjkKBQcsZUj7VLWR0H5cKRA==", "optional": true }, + "node_modules/@types/mdast": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/micromatch": { "version": "2.3.31", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-2.3.31.tgz", @@ -4822,6 +4834,12 @@ "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", "optional": true }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, "node_modules/@types/vinyl": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.7.tgz", @@ -7408,6 +7426,36 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -10593,6 +10641,21 @@ "node": ">=10" } }, + "node_modules/eslint-plugin-markdown": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-3.0.0.tgz", + "integrity": "sha512-hRs5RUJGbeHDLfS7ELanT0e29Ocyssf/7kBM+p7KluY5AwngGkDf8Oyu4658/NZSGTTq05FZeWbkxXtbVyHPwg==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^0.8.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/eslint-plugin-n": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.0.tgz", @@ -11605,6 +11668,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dev": true, + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -12181,6 +12257,15 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/formidable": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", @@ -14197,6 +14282,30 @@ "node": ">=0.10.0" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -14344,6 +14453,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -14418,6 +14537,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-installed-globally": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", @@ -18410,6 +18539,46 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-0.2.0.tgz", + "integrity": "sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==", + "dev": true, + "dependencies": { + "micromark-extension-frontmatter": "^0.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", @@ -18517,6 +18686,39 @@ "node": ">= 0.6" } }, + "node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-0.2.2.tgz", + "integrity": "sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==", + "dev": true, + "dependencies": { + "fault": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -20025,6 +20227,24 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -20621,6 +20841,22 @@ "node": ">=10" } }, + "node_modules/postcss-markdown": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-1.2.0.tgz", + "integrity": "sha512-sO7eeu6pq5F0lx3XavY/rBVmifXbMTd6fGRuXaT/Q7wEuIAWTi0E2t747nQ57iVz99WynTPls4mw5wlLvZLFzw==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^0.8.5", + "mdast-util-frontmatter": "^0.2.0", + "micromark-extension-frontmatter": "^0.2.2", + "postcss": "^8.4.0", + "postcss-safe-parser": "^6.0.0" + }, + "engines": { + "node": "^12 || >=14" + } + }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -25757,6 +25993,19 @@ "node": ">=8" } }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", diff --git a/package.json b/package.json index 3865e56019..c33b9c7df0 100644 --- a/package.json +++ b/package.json @@ -36,11 +36,11 @@ "lint": "npm run lint:editorconfig && npm run lint:prettier && npm run lint:js && npm run lint:scss", "lint:editorconfig": "npm run lint:editorconfig:cli", "lint:editorconfig:cli": "editorconfig-checker", - "lint:js": "npm run lint:js:cli -- \"**/*.{cjs,js,mjs}\"", + "lint:js": "npm run lint:js:cli -- \"**/*.{cjs,js,md,mjs}\"", "lint:js:cli": "eslint --cache --cache-location .cache/eslint --cache-strategy content --color --ignore-path .gitignore --max-warnings 0", "lint:prettier": "npm run lint:prettier:cli -- \"**/*.{json,md,yaml,yml}\"", "lint:prettier:cli": "prettier --cache --cache-location .cache/prettier --cache-strategy content --check", - "lint:scss": "npm run lint:scss:cli -- \"**/*.scss\"", + "lint:scss": "npm run lint:scss:cli -- \"**/*.{md,scss}\"", "lint:scss:cli": "stylelint --cache --cache-location .cache/stylelint --cache-strategy content --color --ignore-path .gitignore --max-warnings 0", "prepare": "node -e \"try { require('husky').install() } catch (e) {if (e.code !== 'MODULE_NOT_FOUND') throw e}\"" }, @@ -56,6 +56,7 @@ "eslint-plugin-es-x": "^7.1.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsdoc": "^46.2.4", + "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-n": "^16.0.0", "eslint-plugin-promise": "^6.1.1", "govuk-frontend-config": "*", @@ -70,6 +71,8 @@ "jest-puppeteer": "^9.0.0", "jest-serializer-html": "^7.1.0", "lint-staged": "^13.2.2", + "postcss-markdown": "^1.2.0", + "postcss-scss": "^4.0.6", "prettier": "^2.8.8", "standard": "^17.1.0", "stylelint": "^14.16.1", diff --git a/packages/govuk-frontend-review/tasks/watch.mjs b/packages/govuk-frontend-review/tasks/watch.mjs index 59c3d60200..ce91210f0e 100644 --- a/packages/govuk-frontend-review/tasks/watch.mjs +++ b/packages/govuk-frontend-review/tasks/watch.mjs @@ -42,7 +42,7 @@ export const watch = (options) => gulp.parallel( task.name('lint:js watch', () => gulp.watch([ `${slash(paths.app)}/src/javascripts/**/*.mjs` - ], npm.script('lint:js:cli', [slash(join(options.workspace, '**/*.{cjs,js,mjs}'))])) + ], npm.script('lint:js:cli', [slash(join(options.workspace, '**/*.{cjs,js,md,mjs}'))])) ), /** diff --git a/packages/govuk-frontend/src/govuk/components/details/implementation.md b/packages/govuk-frontend/src/govuk/components/details/implementation.md index 6e9f7cd1ff..ed982510ff 100644 --- a/packages/govuk-frontend/src/govuk/components/details/implementation.md +++ b/packages/govuk-frontend/src/govuk/components/details/implementation.md @@ -11,8 +11,8 @@ outline around the summary text, which means that the arrow no longer shows up. Previously in GOV.UK Elements we resolved this by targeting Firefox specifically and reverting to `display: list-item`: -``` -@-moz-document regexp('.*') { +```scss +@-moz-document regexp(".*") { details summary:not([tabindex]) { // Allow duplicate properties, override the summary display property // scss-lint:disable DuplicateProperty diff --git a/packages/govuk-frontend/tasks/watch.mjs b/packages/govuk-frontend/tasks/watch.mjs index 89741d69be..3a2b11f4d1 100644 --- a/packages/govuk-frontend/tasks/watch.mjs +++ b/packages/govuk-frontend/tasks/watch.mjs @@ -36,7 +36,7 @@ export const watch = (options) => gulp.parallel( gulp.watch([ `${slash(options.srcPath)}/govuk/**/*.mjs` ], gulp.parallel( - npm.script('lint:js:cli', [slash(join(options.workspace, '**/*.{cjs,js,mjs}'))]), + npm.script('lint:js:cli', [slash(join(options.workspace, '**/*.{cjs,js,md,mjs}'))]), scripts(options) )) ), diff --git a/stylelint.config.js b/stylelint.config.js index e6a5098be4..dc93459836 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -5,9 +5,46 @@ module.exports = { '**/vendor/**', // Ignore CSS-in-JS (including dotfiles) - '**/?(.)*.{cjs,js,mjs}' + '**/?(.)*.{cjs,js,mjs}', + + // Prevent CHANGELOG history changes + 'CHANGELOG.md' ], overrides: [ + { + customSyntax: 'postcss-markdown', + files: ['**/*.md'] + }, + { + customSyntax: 'postcss-markdown', + files: ['**/coding-standards/css.md'], + rules: { + // Allow markdown `*.md` CSS bad examples + 'block-closing-brace-space-before': null, + 'block-no-empty': null, + 'block-opening-brace-space-after': null, + 'color-hex-case': null, + 'color-hex-length': null, + 'declaration-block-single-line-max-declarations': null, + 'declaration-block-trailing-semicolon': null, + 'declaration-colon-space-after': null, + 'length-zero-no-unit': null, + 'number-leading-zero': null, + 'number-no-trailing-zeros': null, + 'rule-empty-line-before': null, + 'selector-max-id': null, + 'string-quotes': null, + 'unit-no-unknown': null, + + // Allow markdown `*.md` Sass bad examples + 'scss/at-if-no-null': null, + 'scss/at-import-no-partial-leading-underscore': null, + 'scss/at-import-partial-extension': null, + 'scss/at-mixin-pattern': null, + 'scss/at-rule-conditional-no-parentheses': null, + 'scss/operator-no-unspaced': null + } + }, { customSyntax: 'postcss-scss', files: ['**/*.scss']