@mui/internal-code-infra 0.0.4-canary.1 → 0.0.4-canary.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -8
  3. package/build/babel-config.d.mts +11 -3
  4. package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
  5. package/build/brokenLinksChecker/index.d.mts +192 -2
  6. package/build/changelog/fetchChangelogs.d.mts +3 -0
  7. package/build/changelog/types.d.ts +3 -1
  8. package/build/cli/cmdArgosPush.d.mts +8 -2
  9. package/build/cli/cmdBuild.d.mts +48 -2
  10. package/build/cli/cmdCopyFiles.d.mts +14 -2
  11. package/build/cli/cmdExtractErrorCodes.d.mts +2 -2
  12. package/build/cli/cmdGenerateChangelog.d.mts +17 -2
  13. package/build/cli/cmdGithubAuth.d.mts +2 -2
  14. package/build/cli/cmdListWorkspaces.d.mts +16 -2
  15. package/build/cli/cmdNetlifyIgnore.d.mts +12 -2
  16. package/build/cli/cmdPublish.d.mts +24 -4
  17. package/build/cli/cmdPublishCanary.d.mts +21 -2
  18. package/build/cli/cmdPublishNewPackage.d.mts +10 -2
  19. package/build/cli/cmdRenameScope.d.mts +18 -0
  20. package/build/cli/cmdSetVersionOverrides.d.mts +5 -2
  21. package/build/cli/cmdVale.d.mts +46 -0
  22. package/build/cli/cmdValidateBuiltTypes.d.mts +2 -2
  23. package/build/eslint/baseConfig.d.mts +3 -1
  24. package/build/eslint/mui/rules/disallow-react-api-in-server-components.d.mts +2 -2
  25. package/build/eslint/mui/rules/docgen-ignore-before-comment.d.mts +2 -2
  26. package/build/eslint/mui/rules/no-floating-cleanup.d.mts +5 -0
  27. package/build/eslint/mui/rules/no-guarded-throw.d.mts +31 -0
  28. package/build/eslint/mui/rules/no-presentation-role.d.mts +5 -0
  29. package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +8 -2
  30. package/build/eslint/mui/rules/nodeEnvUtils.d.mts +18 -0
  31. package/build/remark/config.d.mts +43 -0
  32. package/build/remark/createLintTester.d.mts +10 -0
  33. package/build/remark/firstBlockHeading.d.mts +4 -0
  34. package/build/remark/gitDiff.d.mts +2 -0
  35. package/build/remark/noSpaceInLinks.d.mts +2 -0
  36. package/build/remark/straightQuotes.d.mts +2 -0
  37. package/build/remark/tableAlignment.d.mts +2 -0
  38. package/build/remark/terminalLanguage.d.mts +2 -0
  39. package/build/utils/babel.d.mts +7 -1
  40. package/build/utils/build.d.mts +59 -37
  41. package/build/utils/extractErrorCodes.d.mts +9 -0
  42. package/build/utils/git.d.mts +16 -0
  43. package/build/utils/github.d.mts +13 -1
  44. package/build/utils/pnpm.d.mts +200 -7
  45. package/build/utils/testUtils.d.mts +28 -0
  46. package/build/utils/typescript.d.mts +6 -16
  47. package/package.json +83 -56
  48. package/src/babel-config.mjs +9 -3
  49. package/src/brokenLinksChecker/crawlWorker.mjs +240 -0
  50. package/src/brokenLinksChecker/index.mjs +263 -188
  51. package/src/build-env.d.ts +13 -0
  52. package/src/changelog/fetchChangelogs.mjs +8 -2
  53. package/src/changelog/renderChangelog.mjs +1 -1
  54. package/src/changelog/types.ts +3 -1
  55. package/src/cli/cmdBuild.mjs +51 -85
  56. package/src/cli/cmdListWorkspaces.mjs +16 -3
  57. package/src/cli/cmdNetlifyIgnore.mjs +34 -93
  58. package/src/cli/cmdPublish.mjs +105 -36
  59. package/src/cli/cmdPublishCanary.mjs +145 -109
  60. package/src/cli/cmdPublishNewPackage.mjs +31 -8
  61. package/src/cli/cmdRenameScope.mjs +76 -0
  62. package/src/cli/cmdSetVersionOverrides.mjs +8 -9
  63. package/src/cli/cmdVale.mjs +513 -0
  64. package/src/cli/index.mjs +4 -0
  65. package/src/cli/packageJson.d.ts +2 -3
  66. package/src/eslint/baseConfig.mjs +51 -19
  67. package/src/eslint/docsConfig.mjs +2 -1
  68. package/src/eslint/mui/config.mjs +24 -4
  69. package/src/eslint/mui/index.mjs +6 -0
  70. package/src/eslint/mui/rules/no-floating-cleanup.mjs +187 -0
  71. package/src/eslint/mui/rules/no-guarded-throw.mjs +115 -0
  72. package/src/eslint/mui/rules/no-presentation-role.mjs +60 -0
  73. package/src/eslint/mui/rules/nodeEnvUtils.mjs +52 -0
  74. package/src/eslint/mui/rules/require-dev-wrapper.mjs +25 -40
  75. package/src/estree-typescript.d.ts +1 -1
  76. package/src/remark/config.mjs +157 -0
  77. package/src/remark/createLintTester.mjs +19 -0
  78. package/src/remark/firstBlockHeading.mjs +87 -0
  79. package/src/remark/gitDiff.mjs +43 -0
  80. package/src/remark/noSpaceInLinks.mjs +42 -0
  81. package/src/remark/straightQuotes.mjs +31 -0
  82. package/src/remark/tableAlignment.mjs +23 -0
  83. package/src/remark/terminalLanguage.mjs +19 -0
  84. package/src/stylelint/index.mjs +2 -2
  85. package/src/untyped-plugins.d.ts +14 -14
  86. package/src/utils/build.mjs +604 -270
  87. package/src/utils/git.mjs +27 -7
  88. package/src/utils/pnpm.mjs +549 -26
  89. package/src/utils/testUtils.mjs +53 -0
  90. package/src/utils/typescript.mjs +23 -42
  91. package/vale/.vale.ini +1 -0
  92. package/vale/styles/MUI/CorrectReferenceAllCases.yml +42 -0
  93. package/vale/styles/MUI/CorrectRererenceCased.yml +14 -0
  94. package/vale/styles/MUI/GoogleLatin.yml +11 -0
  95. package/vale/styles/MUI/MuiBrandName.yml +22 -0
  96. package/vale/styles/MUI/NoBritish.yml +112 -0
  97. package/vale/styles/MUI/NoCompanyName.yml +17 -0
  98. package/build/markdownlint/duplicate-h1.d.mts +0 -56
  99. package/build/markdownlint/git-diff.d.mts +0 -11
  100. package/build/markdownlint/index.d.mts +0 -52
  101. package/build/markdownlint/straight-quotes.d.mts +0 -11
  102. package/build/markdownlint/table-alignment.d.mts +0 -11
  103. package/build/markdownlint/terminal-language.d.mts +0 -11
  104. package/src/brokenLinksChecker/__fixtures__/static-site/broken-links.html +0 -20
  105. package/src/brokenLinksChecker/__fixtures__/static-site/broken-targets.html +0 -22
  106. package/src/brokenLinksChecker/__fixtures__/static-site/example.md +0 -20
  107. package/src/brokenLinksChecker/__fixtures__/static-site/external-links.html +0 -21
  108. package/src/brokenLinksChecker/__fixtures__/static-site/ignored-page.html +0 -17
  109. package/src/brokenLinksChecker/__fixtures__/static-site/index.html +0 -28
  110. package/src/brokenLinksChecker/__fixtures__/static-site/known-targets.json +0 -5
  111. package/src/brokenLinksChecker/__fixtures__/static-site/nested/page.html +0 -21
  112. package/src/brokenLinksChecker/__fixtures__/static-site/orphaned-page.html +0 -20
  113. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-api-links.html +0 -20
  114. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-custom-targets.html +0 -24
  115. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-ignored-content.html +0 -28
  116. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-known-target-links.html +0 -19
  117. package/src/brokenLinksChecker/__fixtures__/static-site/unclosed-tags.html +0 -1
  118. package/src/brokenLinksChecker/__fixtures__/static-site/valid.html +0 -20
  119. package/src/brokenLinksChecker/__fixtures__/static-site/with-anchors.html +0 -31
  120. package/src/brokenLinksChecker/index.test.ts +0 -261
  121. package/src/changelog/categorizeCommits.test.ts +0 -319
  122. package/src/changelog/filterCommits.test.ts +0 -363
  123. package/src/changelog/parseCommitLabels.test.ts +0 -509
  124. package/src/changelog/renderChangelog.test.ts +0 -378
  125. package/src/changelog/sortSections.test.ts +0 -199
  126. package/src/eslint/mui/rules/add-undef-to-optional.test.mjs +0 -361
  127. package/src/eslint/mui/rules/consistent-production-guard.test.mjs +0 -162
  128. package/src/eslint/mui/rules/disallow-active-elements-as-key-event-target.test.mjs +0 -66
  129. package/src/eslint/mui/rules/disallow-react-api-in-server-components.test.mjs +0 -305
  130. package/src/eslint/mui/rules/docgen-ignore-before-comment.test.mjs +0 -52
  131. package/src/eslint/mui/rules/flatten-parentheses.test.mjs +0 -245
  132. package/src/eslint/mui/rules/mui-name-matches-component-name.test.mjs +0 -247
  133. package/src/eslint/mui/rules/no-empty-box.test.mjs +0 -40
  134. package/src/eslint/mui/rules/no-styled-box.test.mjs +0 -73
  135. package/src/eslint/mui/rules/require-dev-wrapper.test.mjs +0 -265
  136. package/src/eslint/mui/rules/rules-of-use-theme-variants.test.mjs +0 -149
  137. package/src/eslint/mui/rules/straight-quotes.test.mjs +0 -67
  138. package/src/markdownlint/duplicate-h1.mjs +0 -69
  139. package/src/markdownlint/git-diff.mjs +0 -31
  140. package/src/markdownlint/index.mjs +0 -62
  141. package/src/markdownlint/straight-quotes.mjs +0 -26
  142. package/src/markdownlint/table-alignment.mjs +0 -42
  143. package/src/markdownlint/terminal-language.mjs +0 -19
  144. package/src/utils/build.test.mjs +0 -705
  145. package/src/utils/template.test.mjs +0 -133
  146. package/src/utils/typescript.test.mjs +0 -380
@@ -39,3 +39,55 @@ export function isLiteralEq(node, value) {
39
39
  export function isLiteralNeq(node, value) {
40
40
  return node.type === 'Literal' && node.value !== value;
41
41
  }
42
+
43
+ /**
44
+ * Checks if a BinaryExpression compares process.env.NODE_ENV with === or !==
45
+ * @param {import('estree').Node} node
46
+ * @returns {boolean}
47
+ */
48
+ export function isNodeEnvBinaryComparison(node) {
49
+ return (
50
+ node.type === 'BinaryExpression' &&
51
+ (node.operator === '===' || node.operator === '!==') &&
52
+ (isProcessEnvNodeEnv(node.left) || isProcessEnvNodeEnv(node.right))
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Walks up the parent chain and checks if the node is inside an IfStatement
58
+ * whose test is a NODE_ENV binary comparison.
59
+ * If a callback is provided, it is called with the IfStatement and the direct
60
+ * child that leads to the node. The function returns true only when the callback
61
+ * returns true. Without a callback the function returns true when the node is
62
+ * inside any branch (consequent or alternate) of such an IfStatement.
63
+ * @param {import('eslint').Rule.Node} node
64
+ * @param {(ifStatement: import('estree').IfStatement & import('eslint').Rule.NodeParentExtension, child: import('eslint').Rule.Node) => boolean} [callback]
65
+ * @returns {boolean}
66
+ */
67
+ export function isInsideNodeEnvCheck(node, callback) {
68
+ /** @type {import('eslint').Rule.Node | null} */
69
+ let current = node.parent;
70
+ /** @type {import('eslint').Rule.Node} */
71
+ let currentChild = node;
72
+
73
+ while (current) {
74
+ if (current.type === 'IfStatement' && isNodeEnvBinaryComparison(current.test)) {
75
+ if (callback) {
76
+ if (callback(current, currentChild)) {
77
+ return true;
78
+ }
79
+ } else {
80
+ const isInConsequent = current.consequent === currentChild;
81
+ const isInAlternate = current.alternate === currentChild;
82
+ if (isInConsequent || isInAlternate) {
83
+ return true;
84
+ }
85
+ }
86
+ }
87
+
88
+ currentChild = current;
89
+ current = current.parent;
90
+ }
91
+
92
+ return false;
93
+ }
@@ -1,4 +1,9 @@
1
- import { isProcessEnvNodeEnv, isLiteralEq, isLiteralNeq } from './nodeEnvUtils.mjs';
1
+ import {
2
+ isProcessEnvNodeEnv,
3
+ isLiteralEq,
4
+ isLiteralNeq,
5
+ isInsideNodeEnvCheck,
6
+ } from './nodeEnvUtils.mjs';
2
7
 
3
8
  /**
4
9
  * ESLint rule that enforces certain function calls to be wrapped with
@@ -58,18 +63,21 @@ const rule = {
58
63
  const functionNames = options.functionNames || ['warnOnce', 'warn', 'checkSlot'];
59
64
 
60
65
  /**
61
- * Checks if a binary expression is comparing process.env.NODE_ENV appropriately
62
- * @param {import('estree').BinaryExpression} binaryExpression - The binary expression to check
66
+ * Checks if an expression is comparing process.env.NODE_ENV appropriately
67
+ * @param {import('estree').Expression} expression - The expression to check
63
68
  * @param {string} operator - The expected comparison operator (===, !==, etc.)
64
69
  * @param {string} value - The value to compare with
65
70
  * @returns {boolean}
66
71
  */
67
- function isNodeEnvComparison(binaryExpression, operator, value) {
68
- const { left, right } = binaryExpression;
72
+ function isNodeEnvComparison(expression, operator, value) {
73
+ if (expression.type !== 'BinaryExpression') {
74
+ return false;
75
+ }
76
+ const { left, right } = expression;
69
77
 
70
78
  // Check for exact match with the specified value
71
79
  if (
72
- binaryExpression.operator === operator &&
80
+ expression.operator === operator &&
73
81
  ((isProcessEnvNodeEnv(left) && isLiteralEq(right, value)) ||
74
82
  (isProcessEnvNodeEnv(right) && isLiteralEq(left, value)))
75
83
  ) {
@@ -79,7 +87,7 @@ const rule = {
79
87
  // For !== operator also allow === with any literal value that's NOT 'production'
80
88
  if (
81
89
  operator === '!==' &&
82
- binaryExpression.operator === '===' &&
90
+ expression.operator === '===' &&
83
91
  ((isProcessEnvNodeEnv(left) && isLiteralNeq(right, value)) ||
84
92
  (isProcessEnvNodeEnv(right) && isLiteralNeq(left, value)))
85
93
  ) {
@@ -95,41 +103,18 @@ const rule = {
95
103
  * @returns {boolean}
96
104
  */
97
105
  function isWrappedInProductionCheck(node) {
98
- /** @type {import('eslint').Rule.Node | null} */
99
- let current = node.parent;
100
- /** @type {import('eslint').Rule.Node} */
101
- let currentChild = node;
102
-
103
- while (current) {
104
- // Check if we're inside an if statement
105
- if (current.type === 'IfStatement') {
106
- // Determine which branch we're in
107
- const isInConsequent = current.consequent === currentChild;
108
- const isInAlternate = current.alternate === currentChild;
109
-
110
- // Skip if not in a branch
111
- if (isInConsequent || isInAlternate) {
112
- const test = current.test;
106
+ return isInsideNodeEnvCheck(node, (ifStatement, child) => {
107
+ const isInConsequent = ifStatement.consequent === child;
108
+ const isInAlternate = ifStatement.alternate === child;
113
109
 
114
- // If we're in the consequent, we need !==
115
- // If we're in the alternate (else), we need ===
116
- const operator = isInConsequent ? '!==' : '===';
117
-
118
- // Check for the specific pattern with the right operator
119
- if (
120
- test.type === 'BinaryExpression' &&
121
- isNodeEnvComparison(test, operator, 'production')
122
- ) {
123
- return true;
124
- }
125
- }
110
+ if (isInConsequent || isInAlternate) {
111
+ // If we're in the consequent, we need !==
112
+ // If we're in the alternate (else), we need ===
113
+ const operator = isInConsequent ? '!==' : '===';
114
+ return isNodeEnvComparison(ifStatement.test, operator, 'production');
126
115
  }
127
-
128
- currentChild = current;
129
- current = current.parent;
130
- }
131
-
132
- return false;
116
+ return false;
117
+ });
133
118
  }
134
119
 
135
120
  return {
@@ -1,4 +1,4 @@
1
- import { BaseExpression, Expression } from 'estree';
1
+ import type { BaseExpression, Expression } from 'estree';
2
2
 
3
3
  export * from 'estree';
4
4
 
@@ -0,0 +1,157 @@
1
+ import * as path from 'node:path';
2
+ import { minimatch } from 'minimatch';
3
+ import { unified } from 'unified';
4
+ import remarkFrontmatter from 'remark-frontmatter';
5
+ import remarkGfm from 'remark-gfm';
6
+ import remarkLint from 'remark-lint';
7
+ import remarkLintCodeBlockStyle from 'remark-lint-code-block-style';
8
+ import remarkLintFencedCodeFlag from 'remark-lint-fenced-code-flag';
9
+ import remarkLintHeadingIncrement from 'remark-lint-heading-increment';
10
+ import remarkLintHeadingStyle from 'remark-lint-heading-style';
11
+ import remarkLintNoDuplicateHeadings from 'remark-lint-no-duplicate-headings';
12
+ import remarkLintNoEmptyUrl from 'remark-lint-no-empty-url';
13
+ import remarkLintNoHeadingPunctuation from 'remark-lint-no-heading-punctuation';
14
+ import remarkLintNoMultipleToplevelHeadings from 'remark-lint-no-multiple-toplevel-headings';
15
+ import remarkLintNoUndefinedReferences from 'remark-lint-no-undefined-references';
16
+ import remarkLintNoUnusedDefinitions from 'remark-lint-no-unused-definitions';
17
+ import remarkLintTablePipes from 'remark-lint-table-pipes';
18
+ import muiFirstBlockHeading from './firstBlockHeading.mjs';
19
+ import muiGitDiff from './gitDiff.mjs';
20
+ import muiNoSpaceInLinks from './noSpaceInLinks.mjs';
21
+ import muiStraightQuotes from './straightQuotes.mjs';
22
+ import muiTableAlignment from './tableAlignment.mjs';
23
+ import muiTerminalLanguage from './terminalLanguage.mjs';
24
+
25
+ const GITHUB_ALERT_LABELS = ['!NOTE', '!TIP', '!WARNING', '!IMPORTANT', '!CAUTION'];
26
+
27
+ const RULES = {
28
+ 'no-duplicate-headings': [remarkLintNoDuplicateHeadings, ['error']],
29
+ 'no-multiple-toplevel-headings': [remarkLintNoMultipleToplevelHeadings, ['error']],
30
+ 'no-undefined-references': [
31
+ remarkLintNoUndefinedReferences,
32
+ ['error', { allow: GITHUB_ALERT_LABELS, allowShortcutLink: true }],
33
+ ],
34
+ 'no-unused-definitions': [remarkLintNoUnusedDefinitions, ['error']],
35
+ 'heading-style': [remarkLintHeadingStyle, ['error', 'atx']],
36
+ 'heading-increment': [remarkLintHeadingIncrement, ['error']],
37
+ 'no-heading-punctuation': [remarkLintNoHeadingPunctuation, ['error', '.,;:!']],
38
+ 'code-block-style': [remarkLintCodeBlockStyle, ['error', 'fenced']],
39
+ 'fenced-code-flag': [remarkLintFencedCodeFlag, ['error']],
40
+ 'no-empty-url': [remarkLintNoEmptyUrl, ['error']],
41
+ 'table-pipes': [remarkLintTablePipes, ['error']],
42
+ 'mui-first-block-heading': [muiFirstBlockHeading, ['error']],
43
+ 'mui-git-diff': [muiGitDiff, ['error']],
44
+ 'mui-no-space-in-links': [muiNoSpaceInLinks, ['error']],
45
+ 'mui-straight-quotes': [muiStraightQuotes, ['error']],
46
+ 'mui-table-alignment': [muiTableAlignment, ['error']],
47
+ 'mui-terminal-language': [muiTerminalLanguage, ['error']],
48
+ };
49
+
50
+ /**
51
+ * @param {string | undefined} filePath
52
+ */
53
+ function relativePath(filePath) {
54
+ if (!filePath) {
55
+ return filePath;
56
+ }
57
+ if (path.isAbsolute(filePath)) {
58
+ return path.relative(process.cwd(), filePath);
59
+ }
60
+ return filePath;
61
+ }
62
+
63
+ /**
64
+ * Wraps a remark-lint plugin so its transformer dispatches at runtime based on
65
+ * `file.path`. Each variant (base + per-override) runs through its own mini
66
+ * unified pipeline so severity, baked in by `unified-lint-rule` at attach time,
67
+ * is preserved correctly.
68
+ *
69
+ * @param {string} name
70
+ * @param {import('unified').Plugin<any[], any, any>} plugin
71
+ * @param {any} baseSettings
72
+ * @param {Array<{ files: string | string[], settings: false | any }>} overrideEntries
73
+ */
74
+ function withOverrides(name, plugin, baseSettings, overrideEntries) {
75
+ const baseProcessor = unified().use(plugin, baseSettings);
76
+ const variants = overrideEntries.map(({ files, settings }) => ({
77
+ files: Array.isArray(files) ? files : [files],
78
+ processor: settings === false ? null : unified().use(plugin, settings),
79
+ }));
80
+ function wrapper() {
81
+ /** @type {import('unified').Transformer} */
82
+ return async function transformer(tree, file) {
83
+ const candidate = relativePath(file.path);
84
+ const matched = candidate
85
+ ? variants.find((variant) => variant.files.some((pattern) => minimatch(candidate, pattern)))
86
+ : undefined;
87
+ if (matched) {
88
+ if (matched.processor) {
89
+ await matched.processor.run(tree, file);
90
+ }
91
+ return;
92
+ }
93
+ await baseProcessor.run(tree, file);
94
+ };
95
+ }
96
+ Object.defineProperty(wrapper, 'name', { value: `mui-remark-overrides(${name})` });
97
+ return wrapper;
98
+ }
99
+
100
+ /**
101
+ * Returns a remark preset wiring the MUI-authored remark-lint plugins together
102
+ * with a curated set of community plugins. Drop this into `.remarkrc.mjs`:
103
+ *
104
+ * ```js
105
+ * import { createRemarkConfig } from '@mui/internal-code-infra/remark';
106
+ * export default createRemarkConfig();
107
+ * ```
108
+ *
109
+ * Pass `overrides` to scope rule changes to a glob. Each entry's `rules` map
110
+ * is keyed by the rule name (the key used in `RULES`); `false` disables the
111
+ * rule for matching files, a settings tuple replaces its severity/options:
112
+ *
113
+ * ```js
114
+ * createRemarkConfig({
115
+ * overrides: [
116
+ * { files: 'docs/special/**', rules: { 'mui-no-space-in-links': false } },
117
+ * { files: '**\/CHANGELOG.md', rules: { 'heading-style': ['warn', 'atx'] } },
118
+ * ],
119
+ * });
120
+ * ```
121
+ *
122
+ * @param {Object} [options]
123
+ * @param {Array<{ files: string | string[], rules: Record<string, false | unknown[]> }>} [options.overrides]
124
+ */
125
+ export function createRemarkConfig({ overrides = [] } = {}) {
126
+ for (const override of overrides) {
127
+ const unknown = Object.keys(override.rules).filter((ruleName) => !(ruleName in RULES));
128
+ if (unknown.length > 0) {
129
+ throw new Error(`Unknown remark-lint rule name(s): ${unknown.join(', ')}`);
130
+ }
131
+ }
132
+
133
+ const entries = Object.entries(RULES).map(([name, entry]) => {
134
+ const [plugin, baseSettings] = /** @type {[import('unified').Plugin<any[], any, any>, any]} */ (
135
+ entry
136
+ );
137
+ const overrideEntries = overrides
138
+ .filter((override) => name in override.rules)
139
+ .map((override) => ({ files: override.files, settings: override.rules[name] }));
140
+ if (overrideEntries.length === 0) {
141
+ return [plugin, baseSettings];
142
+ }
143
+ return [withOverrides(name, plugin, baseSettings, overrideEntries)];
144
+ });
145
+
146
+ return {
147
+ settings: {
148
+ bullet: '-',
149
+ emphasis: '_',
150
+ fence: '`',
151
+ listItemIndent: 'one',
152
+ resourceLink: true,
153
+ rule: '-',
154
+ },
155
+ plugins: [[remarkFrontmatter, ['yaml', 'toml']], remarkGfm, remarkLint, ...entries],
156
+ };
157
+ }
@@ -0,0 +1,19 @@
1
+ import { remark } from 'remark';
2
+ import remarkGfm from 'remark-gfm';
3
+
4
+ /**
5
+ * @param {unknown} plugin
6
+ * @param {unknown} [options]
7
+ * @returns {(input: string) => Array<{ reason: string, line: number, column: number }>}
8
+ */
9
+ export function createLintTester(plugin, options) {
10
+ const entry = /** @type {any} */ (options === undefined ? plugin : [plugin, options]);
11
+ return function lint(input) {
12
+ const file = remark().use(remarkGfm).use(entry).processSync(input);
13
+ return file.messages.map((message) => ({
14
+ reason: message.reason,
15
+ line: message.line ?? 0,
16
+ column: message.column ?? 0,
17
+ }));
18
+ };
19
+ }
@@ -0,0 +1,87 @@
1
+ import { lintRule } from 'unified-lint-rule';
2
+
3
+ const FRONTMATTER_TYPES = new Set(['yaml', 'toml']);
4
+ const INVISIBLE_TAGS = new Set(['style', 'script']);
5
+ const DEFAULT_FRONT_MATTER_TITLE = /^\s*"?title"?\s*[:=]/m;
6
+
7
+ /** @param {import('mdast').RootContent} node */
8
+ const isSkippable = (node) => {
9
+ const type = /** @type {string} */ (node.type);
10
+ if (FRONTMATTER_TYPES.has(type)) {
11
+ return true;
12
+ }
13
+ if (type === 'html') {
14
+ const value = /** @type {{ value: string }} */ (/** @type {unknown} */ (node)).value.trim();
15
+ if (value.startsWith('<!--')) {
16
+ return true;
17
+ }
18
+ const match = value.match(/^<\s*([a-zA-Z][a-zA-Z0-9-]*)/);
19
+ if (match && INVISIBLE_TAGS.has(match[1].toLowerCase())) {
20
+ return true;
21
+ }
22
+ }
23
+ if (type === 'mdxJsxFlowElement') {
24
+ const name = /** @type {{ name: string | null }} */ (/** @type {unknown} */ (node)).name;
25
+ if (name && INVISIBLE_TAGS.has(name.toLowerCase())) {
26
+ return true;
27
+ }
28
+ }
29
+ if (type === 'mdxjsEsm') {
30
+ return true;
31
+ }
32
+ if (type === 'mdxFlowExpression') {
33
+ const value = /** @type {{ value: string }} */ (/** @type {unknown} */ (node)).value.trim();
34
+ if (
35
+ value === '' ||
36
+ (value.startsWith('/*') && value.endsWith('*/')) ||
37
+ value.startsWith('//')
38
+ ) {
39
+ return true;
40
+ }
41
+ }
42
+ return false;
43
+ };
44
+
45
+ /**
46
+ * @param {import('mdast').Root} tree
47
+ * @param {RegExp | false} pattern
48
+ */
49
+ const hasFrontMatterTitle = (tree, pattern) => {
50
+ if (!pattern) {
51
+ return false;
52
+ }
53
+ const frontMatter = tree.children.find((child) => FRONTMATTER_TYPES.has(child.type));
54
+ if (!frontMatter) {
55
+ return false;
56
+ }
57
+ return pattern.test(
58
+ /** @type {{ value: string }} */ (/** @type {unknown} */ (frontMatter)).value,
59
+ );
60
+ };
61
+
62
+ const remarkLintMuiFirstBlockHeading = lintRule(
63
+ {
64
+ origin: 'remark-lint:mui-first-block-heading',
65
+ url: 'https://github.com/mui/mui-public',
66
+ },
67
+ /** @type {import('unified-lint-rule').Rule<import('mdast').Root, { frontMatterTitle?: RegExp | false } | undefined>} */
68
+ (tree, file, options) => {
69
+ const frontMatterTitle =
70
+ options?.frontMatterTitle === undefined
71
+ ? DEFAULT_FRONT_MATTER_TITLE
72
+ : options.frontMatterTitle;
73
+ if (hasFrontMatterTitle(tree, frontMatterTitle)) {
74
+ return;
75
+ }
76
+ const firstBlock = tree.children.find((child) => !isSkippable(child));
77
+ if (!firstBlock) {
78
+ file.message('Documents must begin with a top-level heading.');
79
+ return;
80
+ }
81
+ if (firstBlock.type !== 'heading' || firstBlock.depth !== 1) {
82
+ file.message('Documents must begin with a top-level heading.', firstBlock);
83
+ }
84
+ },
85
+ );
86
+
87
+ export default remarkLintMuiFirstBlockHeading;
@@ -0,0 +1,43 @@
1
+ import { lintRule } from 'unified-lint-rule';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ const singleCharPrefixes = new Set([' ', '-', '+']);
5
+ const linePrefixes = ['@@ ', 'diff --git ', 'index '];
6
+
7
+ const remarkLintMuiGitDiff = lintRule(
8
+ {
9
+ origin: 'remark-lint:mui-git-diff',
10
+ url: 'https://github.com/mui/mui-public',
11
+ },
12
+ /** @param {import('mdast').Root} tree */
13
+ (tree, file) => {
14
+ visit(tree, 'code', (node) => {
15
+ if (node.lang !== 'diff' || !node.position) {
16
+ return;
17
+ }
18
+ const contentStartLine = node.position.start.line + 1;
19
+ const lines = node.value.split('\n');
20
+ lines.forEach((line, index) => {
21
+ if (line === '') {
22
+ return;
23
+ }
24
+ if (singleCharPrefixes.has(line[0])) {
25
+ return;
26
+ }
27
+ if (linePrefixes.some((prefix) => line.startsWith(prefix))) {
28
+ return;
29
+ }
30
+ const lineNumber = contentStartLine + index;
31
+ file.message(
32
+ 'Line in a `diff` code block must start with " ", "+", "-", "@@ ", "diff --git ", or "index ".',
33
+ {
34
+ start: { line: lineNumber, column: 1 },
35
+ end: { line: lineNumber, column: line.length + 1 },
36
+ },
37
+ );
38
+ });
39
+ });
40
+ },
41
+ );
42
+
43
+ export default remarkLintMuiGitDiff;
@@ -0,0 +1,42 @@
1
+ import { lintRule } from 'unified-lint-rule';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ const remarkLintMuiNoSpaceInLinks = lintRule(
5
+ {
6
+ origin: 'remark-lint:mui-no-space-in-links',
7
+ url: 'https://github.com/mui/mui-public',
8
+ },
9
+ /** @param {import('mdast').Root} tree */
10
+ (tree, file) => {
11
+ const source = String(file.value);
12
+ visit(tree, 'link', (node) => {
13
+ if (!node.position || node.children.length === 0) {
14
+ return;
15
+ }
16
+ const first = node.children[0];
17
+ const last = node.children[node.children.length - 1];
18
+ if (!first.position || !last.position) {
19
+ return;
20
+ }
21
+ const textStart = first.position.start.offset;
22
+ const textEnd = last.position.end.offset;
23
+ if (textStart === undefined || textEnd === undefined) {
24
+ return;
25
+ }
26
+ const openBracket = source.lastIndexOf('[', textStart);
27
+ const closeBracket = source.indexOf(']', textEnd);
28
+ if (openBracket === -1 || closeBracket === -1) {
29
+ return;
30
+ }
31
+ const innerText = source.slice(openBracket + 1, closeBracket);
32
+ if (innerText.length === 0) {
33
+ return;
34
+ }
35
+ if (innerText !== innerText.trim()) {
36
+ file.message('Link text should not start or end with whitespace.', node);
37
+ }
38
+ });
39
+ },
40
+ );
41
+
42
+ export default remarkLintMuiNoSpaceInLinks;
@@ -0,0 +1,31 @@
1
+ import { lintRule } from 'unified-lint-rule';
2
+
3
+ const curlyQuotes = new Set(['‘', '’', '“', '”']);
4
+
5
+ const remarkLintMuiStraightQuotes = lintRule(
6
+ {
7
+ origin: 'remark-lint:mui-straight-quotes',
8
+ url: 'https://github.com/mui/mui-public',
9
+ },
10
+ /** @param {import('mdast').Root} _tree */
11
+ (_tree, file) => {
12
+ const text = String(file.value);
13
+ let line = 1;
14
+ let lineStart = 0;
15
+ for (let index = 0; index < text.length; index += 1) {
16
+ const char = text[index];
17
+ if (char === '\n') {
18
+ line += 1;
19
+ lineStart = index + 1;
20
+ } else if (curlyQuotes.has(char)) {
21
+ const column = index - lineStart + 1;
22
+ file.message('Use straight quotes instead of curly quotes.', {
23
+ start: { line, column },
24
+ end: { line, column: column + 1 },
25
+ });
26
+ }
27
+ }
28
+ },
29
+ );
30
+
31
+ export default remarkLintMuiStraightQuotes;
@@ -0,0 +1,23 @@
1
+ import { lintRule } from 'unified-lint-rule';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ const remarkLintMuiTableAlignment = lintRule(
5
+ {
6
+ origin: 'remark-lint:mui-table-alignment',
7
+ url: 'https://github.com/mui/mui-public',
8
+ },
9
+ /** @param {import('mdast').Root} tree */
10
+ (tree, file) => {
11
+ visit(tree, 'table', (node) => {
12
+ const align = node.align ?? [];
13
+ if (align.some((value) => value == null)) {
14
+ file.message(
15
+ 'Table columns should declare an explicit alignment (`:---`, `---:`, or `:---:`).',
16
+ node,
17
+ );
18
+ }
19
+ });
20
+ },
21
+ );
22
+
23
+ export default remarkLintMuiTableAlignment;
@@ -0,0 +1,19 @@
1
+ import { lintRule } from 'unified-lint-rule';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ const remarkLintMuiTerminalLanguage = lintRule(
5
+ {
6
+ origin: 'remark-lint:mui-terminal-language',
7
+ url: 'https://github.com/mui/mui-public',
8
+ },
9
+ /** @param {import('mdast').Root} tree */
10
+ (tree, file) => {
11
+ visit(tree, 'code', (node) => {
12
+ if (node.lang === 'sh' && node.position) {
13
+ file.message('Use `bash` instead of `sh` as the code block language.', node);
14
+ }
15
+ });
16
+ },
17
+ );
18
+
19
+ export default remarkLintMuiTerminalLanguage;
@@ -1,4 +1,4 @@
1
- import postcssStylesSyntax from 'postcss-styled-syntax';
1
+ import * as postcssStyledSyntax from 'postcss-styled-syntax';
2
2
 
3
3
  /** @type {import('stylelint').Config} */
4
4
  export default {
@@ -39,7 +39,7 @@ export default {
39
39
  overrides: [
40
40
  {
41
41
  files: ['**/*.?(c|m)[jt]s?(x)'],
42
- customSyntax: /** @type {any} */ (postcssStylesSyntax),
42
+ customSyntax: postcssStyledSyntax,
43
43
  },
44
44
  ],
45
45
  };