@mui/internal-code-infra 0.0.4-canary.7 → 0.0.4-canary.71
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.
- package/LICENSE +1 -1
- package/README.md +19 -8
- package/build/babel-config.d.mts +11 -3
- package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
- package/build/brokenLinksChecker/index.d.mts +45 -2
- package/build/changelog/types.d.ts +1 -1
- package/build/cli/cmdArgosPush.d.mts +2 -2
- package/build/cli/cmdBuild.d.mts +3 -2
- package/build/cli/cmdCopyFiles.d.mts +2 -2
- package/build/cli/cmdExtractErrorCodes.d.mts +2 -2
- package/build/cli/cmdGenerateChangelog.d.mts +2 -2
- package/build/cli/cmdGithubAuth.d.mts +2 -2
- package/build/cli/cmdListWorkspaces.d.mts +6 -4
- package/build/cli/cmdNetlifyIgnore.d.mts +3 -2
- package/build/cli/cmdPublish.d.mts +4 -2
- package/build/cli/cmdPublishCanary.d.mts +3 -3
- package/build/cli/cmdPublishNewPackage.d.mts +4 -2
- package/build/cli/cmdSetVersionOverrides.d.mts +2 -2
- package/build/cli/cmdVale.d.mts +46 -0
- package/build/cli/cmdValidateBuiltTypes.d.mts +2 -2
- package/build/eslint/baseConfig.d.mts +3 -1
- package/build/eslint/mui/rules/disallow-react-api-in-server-components.d.mts +2 -2
- package/build/eslint/mui/rules/docgen-ignore-before-comment.d.mts +2 -2
- package/build/eslint/mui/rules/no-floating-cleanup.d.mts +5 -0
- package/build/eslint/mui/rules/no-guarded-throw.d.mts +31 -0
- package/build/eslint/mui/rules/no-presentation-role.d.mts +5 -0
- package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +2 -2
- package/build/eslint/mui/rules/nodeEnvUtils.d.mts +18 -0
- package/build/remark/config.d.mts +43 -0
- package/build/remark/createLintTester.d.mts +10 -0
- package/build/remark/firstBlockHeading.d.mts +4 -0
- package/build/remark/gitDiff.d.mts +2 -0
- package/build/remark/noSpaceInLinks.d.mts +2 -0
- package/build/remark/straightQuotes.d.mts +2 -0
- package/build/remark/tableAlignment.d.mts +2 -0
- package/build/remark/terminalLanguage.d.mts +2 -0
- package/build/utils/babel.d.mts +1 -1
- package/build/utils/build.d.mts +56 -37
- package/build/utils/github.d.mts +1 -1
- package/build/utils/pnpm.d.mts +81 -2
- package/build/utils/testUtils.d.mts +7 -0
- package/build/utils/typescript.d.mts +6 -16
- package/package.json +60 -38
- package/src/babel-config.mjs +9 -3
- package/src/brokenLinksChecker/__fixtures__/static-site/index.html +1 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/invalid-html.html +15 -0
- package/src/brokenLinksChecker/crawlWorker.mjs +217 -0
- package/src/brokenLinksChecker/index.mjs +263 -188
- package/src/brokenLinksChecker/index.test.ts +53 -13
- package/src/changelog/categorizeCommits.test.ts +5 -5
- package/src/changelog/fetchChangelogs.mjs +6 -2
- package/src/changelog/parseCommitLabels.test.ts +5 -5
- package/src/changelog/renderChangelog.mjs +1 -1
- package/src/changelog/types.ts +1 -1
- package/src/cli/cmdBuild.mjs +51 -85
- package/src/cli/cmdListWorkspaces.mjs +12 -27
- package/src/cli/cmdNetlifyIgnore.mjs +34 -93
- package/src/cli/cmdPublish.mjs +55 -15
- package/src/cli/cmdPublishCanary.mjs +128 -132
- package/src/cli/cmdPublishNewPackage.mjs +27 -6
- package/src/cli/cmdSetVersionOverrides.mjs +8 -9
- package/src/cli/cmdVale.mjs +513 -0
- package/src/cli/cmdVale.test.mjs +644 -0
- package/src/cli/index.mjs +2 -0
- package/src/cli/packageJson.d.ts +1 -1
- package/src/eslint/baseConfig.mjs +51 -19
- package/src/eslint/docsConfig.mjs +2 -1
- package/src/eslint/mui/config.mjs +24 -4
- package/src/eslint/mui/index.mjs +6 -0
- package/src/eslint/mui/rules/no-floating-cleanup.mjs +187 -0
- package/src/eslint/mui/rules/no-floating-cleanup.test.mjs +141 -0
- package/src/eslint/mui/rules/no-guarded-throw.mjs +115 -0
- package/src/eslint/mui/rules/no-guarded-throw.test.mjs +206 -0
- package/src/eslint/mui/rules/no-presentation-role.mjs +60 -0
- package/src/eslint/mui/rules/no-presentation-role.test.mjs +33 -0
- package/src/eslint/mui/rules/nodeEnvUtils.mjs +52 -0
- package/src/eslint/mui/rules/require-dev-wrapper.mjs +25 -40
- package/src/estree-typescript.d.ts +1 -1
- package/src/remark/config.mjs +157 -0
- package/src/remark/createLintTester.mjs +19 -0
- package/src/remark/firstBlockHeading.mjs +87 -0
- package/src/remark/firstBlockHeading.test.mjs +107 -0
- package/src/remark/gitDiff.mjs +43 -0
- package/src/remark/gitDiff.test.mjs +45 -0
- package/src/remark/noSpaceInLinks.mjs +42 -0
- package/src/remark/noSpaceInLinks.test.mjs +22 -0
- package/src/remark/straightQuotes.mjs +31 -0
- package/src/remark/straightQuotes.test.mjs +25 -0
- package/src/remark/tableAlignment.mjs +23 -0
- package/src/remark/tableAlignment.test.mjs +28 -0
- package/src/remark/terminalLanguage.mjs +19 -0
- package/src/remark/terminalLanguage.test.mjs +17 -0
- package/src/untyped-plugins.d.ts +11 -11
- package/src/utils/build.mjs +604 -270
- package/src/utils/build.test.mjs +1079 -521
- package/src/utils/git.mjs +16 -7
- package/src/utils/pnpm.mjs +255 -3
- package/src/utils/pnpm.test.mjs +731 -0
- package/src/utils/testUtils.mjs +18 -0
- package/src/utils/typescript.mjs +23 -42
- package/src/utils/typescript.test.mjs +234 -273
- package/vale/.vale.ini +1 -0
- package/vale/styles/MUI/CorrectReferenceAllCases.yml +43 -0
- package/vale/styles/MUI/CorrectRererenceCased.yml +14 -0
- package/vale/styles/MUI/GoogleLatin.yml +11 -0
- package/vale/styles/MUI/MuiBrandName.yml +22 -0
- package/vale/styles/MUI/NoBritish.yml +112 -0
- package/vale/styles/MUI/NoCompanyName.yml +17 -0
- package/build/markdownlint/duplicate-h1.d.mts +0 -56
- package/build/markdownlint/git-diff.d.mts +0 -11
- package/build/markdownlint/index.d.mts +0 -52
- package/build/markdownlint/straight-quotes.d.mts +0 -11
- package/build/markdownlint/table-alignment.d.mts +0 -11
- package/build/markdownlint/terminal-language.d.mts +0 -11
- package/src/markdownlint/duplicate-h1.mjs +0 -69
- package/src/markdownlint/git-diff.mjs +0 -31
- package/src/markdownlint/index.mjs +0 -62
- package/src/markdownlint/straight-quotes.mjs +0 -26
- package/src/markdownlint/table-alignment.mjs +0 -42
- package/src/markdownlint/terminal-language.mjs +0 -19
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import eslint from 'eslint';
|
|
2
|
+
import parser from '@typescript-eslint/parser';
|
|
3
|
+
import rule from './no-guarded-throw.mjs';
|
|
4
|
+
|
|
5
|
+
const ruleTester = new eslint.RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
parser,
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
ruleTester.run('no-guarded-throw', rule, {
|
|
12
|
+
valid: [
|
|
13
|
+
// Should pass: Unconditional throw
|
|
14
|
+
{
|
|
15
|
+
code: `
|
|
16
|
+
throw new Error('Something went wrong');
|
|
17
|
+
`,
|
|
18
|
+
},
|
|
19
|
+
// Should pass: Throw inside a non-NODE_ENV conditional
|
|
20
|
+
{
|
|
21
|
+
code: `
|
|
22
|
+
if (value == null) {
|
|
23
|
+
throw new TypeError('value is required');
|
|
24
|
+
}
|
|
25
|
+
`,
|
|
26
|
+
},
|
|
27
|
+
// Should pass: Throw inside a catch block (no NODE_ENV guard)
|
|
28
|
+
{
|
|
29
|
+
code: `
|
|
30
|
+
try {
|
|
31
|
+
doSomething();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error('Failed');
|
|
34
|
+
}
|
|
35
|
+
`,
|
|
36
|
+
},
|
|
37
|
+
// Should pass: Throw inside arrow function inside NODE_ENV guard
|
|
38
|
+
{
|
|
39
|
+
code: `
|
|
40
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
41
|
+
const fn = () => {
|
|
42
|
+
throw new Error('inside arrow function');
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
`,
|
|
46
|
+
},
|
|
47
|
+
// Should pass: Throw inside function expression inside NODE_ENV guard
|
|
48
|
+
{
|
|
49
|
+
code: `
|
|
50
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
51
|
+
const fn = function () {
|
|
52
|
+
throw new Error('inside function expression');
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
`,
|
|
56
|
+
},
|
|
57
|
+
// Should pass: Throw inside function declaration inside NODE_ENV guard
|
|
58
|
+
{
|
|
59
|
+
code: `
|
|
60
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
61
|
+
function validate() {
|
|
62
|
+
throw new Error('inside function declaration');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
invalid: [
|
|
69
|
+
// Should fail: Throw inside !== 'production' guard
|
|
70
|
+
{
|
|
71
|
+
code: `
|
|
72
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
73
|
+
throw new Error('Dev-only error');
|
|
74
|
+
}
|
|
75
|
+
`,
|
|
76
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
77
|
+
},
|
|
78
|
+
// Should fail: Throw inside === 'production' guard
|
|
79
|
+
{
|
|
80
|
+
code: `
|
|
81
|
+
if (process.env.NODE_ENV === 'production') {
|
|
82
|
+
throw new Error('Prod-only error');
|
|
83
|
+
}
|
|
84
|
+
`,
|
|
85
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
86
|
+
},
|
|
87
|
+
// Should fail: Throw in else block of === 'production' check
|
|
88
|
+
{
|
|
89
|
+
code: `
|
|
90
|
+
if (process.env.NODE_ENV === 'production') {
|
|
91
|
+
// production path
|
|
92
|
+
} else {
|
|
93
|
+
throw new Error('Non-production error');
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
97
|
+
},
|
|
98
|
+
// Should fail: Throw nested inside NODE_ENV guard
|
|
99
|
+
{
|
|
100
|
+
code: `
|
|
101
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
102
|
+
if (value == null) {
|
|
103
|
+
throw new TypeError('value is required');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
`,
|
|
107
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
108
|
+
},
|
|
109
|
+
// Should fail: Reversed comparison (literal on left)
|
|
110
|
+
{
|
|
111
|
+
code: `
|
|
112
|
+
if ('production' !== process.env.NODE_ENV) {
|
|
113
|
+
throw new Error('Dev-only error');
|
|
114
|
+
}
|
|
115
|
+
`,
|
|
116
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
117
|
+
},
|
|
118
|
+
// Should fail: Throw in loop inside NODE_ENV guard
|
|
119
|
+
{
|
|
120
|
+
code: `
|
|
121
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
122
|
+
for (const item of items) {
|
|
123
|
+
throw new Error('Invalid item');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`,
|
|
127
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
128
|
+
},
|
|
129
|
+
// Should fail: NODE_ENV combined with other conditions using &&
|
|
130
|
+
{
|
|
131
|
+
code: `
|
|
132
|
+
if (process.env.NODE_ENV !== 'production' && value == null) {
|
|
133
|
+
throw new TypeError('value is required');
|
|
134
|
+
}
|
|
135
|
+
`,
|
|
136
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
137
|
+
},
|
|
138
|
+
// Should fail: NODE_ENV combined with other conditions using ||
|
|
139
|
+
{
|
|
140
|
+
code: `
|
|
141
|
+
if (condition || process.env.NODE_ENV === 'test') {
|
|
142
|
+
throw new Error('Test or condition error');
|
|
143
|
+
}
|
|
144
|
+
`,
|
|
145
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
146
|
+
},
|
|
147
|
+
// Should fail: Unary not on process.env.NODE_ENV
|
|
148
|
+
{
|
|
149
|
+
code: `
|
|
150
|
+
if (!process.env.NODE_ENV) {
|
|
151
|
+
throw new Error('NODE_ENV not set');
|
|
152
|
+
}
|
|
153
|
+
`,
|
|
154
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
155
|
+
},
|
|
156
|
+
// Should fail: NODE_ENV passed to a function
|
|
157
|
+
{
|
|
158
|
+
code: `
|
|
159
|
+
if (fn(process.env.NODE_ENV)) {
|
|
160
|
+
throw new Error('Function check failed');
|
|
161
|
+
}
|
|
162
|
+
`,
|
|
163
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
164
|
+
},
|
|
165
|
+
// Should fail: Throw inside try/catch inside NODE_ENV guard
|
|
166
|
+
{
|
|
167
|
+
code: `
|
|
168
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
169
|
+
try {
|
|
170
|
+
doSomething();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw new Error('caught inside guard');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
`,
|
|
176
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
177
|
+
},
|
|
178
|
+
// Should fail: Throw deeply nested in control flow inside NODE_ENV guard
|
|
179
|
+
{
|
|
180
|
+
code: `
|
|
181
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
182
|
+
if (value == null) {
|
|
183
|
+
for (const item of items) {
|
|
184
|
+
if (!item.valid) {
|
|
185
|
+
throw new Error('invalid item');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
`,
|
|
191
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
192
|
+
},
|
|
193
|
+
// Should fail: Throw inside switch/case inside NODE_ENV guard
|
|
194
|
+
{
|
|
195
|
+
code: `
|
|
196
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
197
|
+
switch (type) {
|
|
198
|
+
case 'a':
|
|
199
|
+
throw new Error('invalid type a');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
`,
|
|
203
|
+
errors: [{ messageId: 'guardedThrow' }],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
3
|
+
*/
|
|
4
|
+
const rule = {
|
|
5
|
+
meta: {
|
|
6
|
+
docs: {
|
|
7
|
+
description:
|
|
8
|
+
'Disallow role="presentation" in favor of role="none". Both are equivalent, but role="none" is clearer and shorter.',
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
noPresentation:
|
|
12
|
+
'Use role="none" instead of role="presentation". They are equivalent, but role="none" is preferred.',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
type: 'suggestion',
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
/** @param {import('estree-jsx').JSXAttribute} node */
|
|
21
|
+
JSXAttribute(node) {
|
|
22
|
+
if (node.name.type !== 'JSXIdentifier' || node.name.name !== 'role') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { value } = node;
|
|
27
|
+
|
|
28
|
+
// role="presentation"
|
|
29
|
+
if (value !== null && value.type === 'Literal' && value.value === 'presentation') {
|
|
30
|
+
context.report({
|
|
31
|
+
node,
|
|
32
|
+
messageId: 'noPresentation',
|
|
33
|
+
fix(fixer) {
|
|
34
|
+
return fixer.replaceText(value, '"none"');
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// role={'presentation'}
|
|
41
|
+
if (
|
|
42
|
+
value !== null &&
|
|
43
|
+
value.type === 'JSXExpressionContainer' &&
|
|
44
|
+
value.expression.type === 'Literal' &&
|
|
45
|
+
value.expression.value === 'presentation'
|
|
46
|
+
) {
|
|
47
|
+
context.report({
|
|
48
|
+
node,
|
|
49
|
+
messageId: 'noPresentation',
|
|
50
|
+
fix(fixer) {
|
|
51
|
+
return fixer.replaceText(value, '"none"');
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default rule;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import eslint from 'eslint';
|
|
2
|
+
import parser from '@typescript-eslint/parser';
|
|
3
|
+
import rule from './no-presentation-role.mjs';
|
|
4
|
+
|
|
5
|
+
const ruleTester = new eslint.RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
parser,
|
|
8
|
+
parserOptions: {
|
|
9
|
+
ecmaFeatures: { jsx: true },
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
ruleTester.run('no-presentation-role', rule, {
|
|
15
|
+
valid: ['<div role="none" />', '<div role="button" />', '<div />', '<div role={presentation} />'],
|
|
16
|
+
invalid: [
|
|
17
|
+
{
|
|
18
|
+
code: '<div role="presentation" />',
|
|
19
|
+
errors: [{ messageId: 'noPresentation' }],
|
|
20
|
+
output: '<div role="none" />',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
code: "<div role={'presentation'} />",
|
|
24
|
+
errors: [{ messageId: 'noPresentation' }],
|
|
25
|
+
output: '<div role="none" />',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
code: '<table role="presentation"><tr><td /></tr></table>',
|
|
29
|
+
errors: [{ messageId: 'noPresentation' }],
|
|
30
|
+
output: '<table role="none"><tr><td /></tr></table>',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
@@ -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 {
|
|
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
|
|
62
|
-
* @param {import('estree').
|
|
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(
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
129
|
-
current = current.parent;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return false;
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
133
118
|
}
|
|
134
119
|
|
|
135
120
|
return {
|
|
@@ -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
|
+
}
|