@mui/internal-code-infra 0.1.1-canary.2 → 0.1.1-canary.20
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/README.md +3 -3
- package/build/changelog/fetchChangelogs.d.mts +9 -10
- package/build/changelog/index.d.mts +1 -0
- package/build/cli/cmdBaseline.d.mts +18 -0
- package/build/cli/cmdNetlifyIgnore.d.mts +1 -1
- package/build/eslint/mui/rules/no-constructor-assigned-field.d.mts +50 -0
- package/build/eslint/packageJsonConfig.d.mts +7 -0
- package/build/remark/config.d.mts +5 -14
- package/build/utils/build.d.mts +14 -0
- package/build/utils/credentials.d.mts +2 -2
- package/build/utils/git.d.mts +30 -9
- package/build/utils/github.d.mts +8 -0
- package/package.json +38 -35
- package/src/changelog/fetchChangelogs.mjs +66 -26
- package/src/changelog/index.mjs +3 -0
- package/src/cli/cmdBaseline.mjs +31 -0
- package/src/cli/cmdBuild.mjs +2 -0
- package/src/cli/cmdNetlifyIgnore.mjs +14 -7
- package/src/cli/index.mjs +2 -0
- package/src/eslint/baseConfig.mjs +2 -0
- package/src/eslint/docsConfig.mjs +28 -10
- package/src/eslint/jsonConfig.mjs +2 -1
- package/src/eslint/mui/config.mjs +16 -1
- package/src/eslint/mui/index.mjs +2 -0
- package/src/eslint/mui/rules/no-constructor-assigned-field.mjs +183 -0
- package/src/eslint/packageJsonConfig.mjs +50 -0
- package/src/eslint/testConfig.mjs +1 -2
- package/src/remark/config.mjs +60 -13
- package/src/utils/build.mjs +34 -0
- package/src/utils/credentials.mjs +4 -3
- package/src/utils/extractErrorCodes.mjs +1 -3
- package/src/utils/git.mjs +107 -46
- package/src/utils/github.mjs +27 -0
- package/vale/styles/MUI/GoogleLatin.yml +5 -2
- package/vale/styles/MUI/CorrectRererenceCased.yml +0 -14
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {Object} Args
|
|
7
|
-
* @property {string[]} workspaces -
|
|
7
|
+
* @property {string[]} workspaces - Workspace names or root-relative paths to process
|
|
8
8
|
* @property {boolean} [check] - Check mode - error if the generated content differs from current
|
|
9
9
|
* @property {string} [baseBranch] - Branch to compare PRs against (default: master)
|
|
10
10
|
*/
|
|
@@ -100,7 +100,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
100
100
|
.positional('workspaces', {
|
|
101
101
|
type: 'string',
|
|
102
102
|
array: true,
|
|
103
|
-
describe: '
|
|
103
|
+
describe: 'Workspace names or root-relative paths to process',
|
|
104
104
|
demandOption: true,
|
|
105
105
|
})
|
|
106
106
|
.option('check', {
|
|
@@ -115,6 +115,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
115
115
|
"Branch to compare PRs against (the site's production branch on Netlify). Production and branch deploys always rebuild regardless of this value.",
|
|
116
116
|
})
|
|
117
117
|
.example('$0 netlify-ignore @mui/internal-docs-infra', 'Update netlify.toml for a workspace')
|
|
118
|
+
.example('$0 netlify-ignore ./apps/dashboard', 'Update an unnamed workspace by path')
|
|
118
119
|
.example(
|
|
119
120
|
'$0 netlify-ignore @mui/internal-docs-infra @mui/internal-code-infra',
|
|
120
121
|
'Update netlify.toml for multiple workspaces',
|
|
@@ -135,11 +136,17 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
135
136
|
|
|
136
137
|
// Get all workspace packages and create workspace map
|
|
137
138
|
const allWorkspaces = await getWorkspacePackages({ cwd: workspaceRoot });
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
|
|
139
|
+
/** @type {Map<string, string>} */
|
|
140
|
+
const workspaceMap = new Map();
|
|
141
|
+
for (const workspace of allWorkspaces) {
|
|
142
|
+
if (workspace.name) {
|
|
143
|
+
workspaceMap.set(workspace.name, workspace.path);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Paths also identify private workspaces that have no package name.
|
|
147
|
+
const relativePath = toPosixPath(path.relative(workspaceRoot, workspace.path));
|
|
148
|
+
workspaceMap.set(`./${relativePath}`, workspace.path);
|
|
149
|
+
}
|
|
143
150
|
|
|
144
151
|
// Process each workspace concurrently
|
|
145
152
|
await Promise.all(
|
package/src/cli/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import yargs from 'yargs';
|
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
5
|
|
|
6
6
|
import cmdArgosPush from './cmdArgosPush.mjs';
|
|
7
|
+
import cmdBaseline from './cmdBaseline.mjs';
|
|
7
8
|
import cmdBuild from './cmdBuild.mjs';
|
|
8
9
|
import cmdCopyFiles from './cmdCopyFiles.mjs';
|
|
9
10
|
import cmdExtractErrorCodes from './cmdExtractErrorCodes.mjs';
|
|
@@ -38,6 +39,7 @@ await yargs(hideBin(process.argv))
|
|
|
38
39
|
globalArgv = argv;
|
|
39
40
|
}, true)
|
|
40
41
|
.command(cmdArgosPush)
|
|
42
|
+
.command(cmdBaseline)
|
|
41
43
|
.command(cmdBuild)
|
|
42
44
|
.command(cmdCopyFiles)
|
|
43
45
|
.command(cmdExtractErrorCodes)
|
|
@@ -17,6 +17,7 @@ import { createCoreConfig } from './mui/config.mjs';
|
|
|
17
17
|
import muiPlugin from './mui/index.mjs';
|
|
18
18
|
import { EXTENSION_TS } from './extensions.mjs';
|
|
19
19
|
import { createJsonConfig } from './jsonConfig.mjs';
|
|
20
|
+
import { createPackageJsonConfig } from './packageJsonConfig.mjs';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* @param {string} filePath
|
|
@@ -73,6 +74,7 @@ export function createBaseConfig({
|
|
|
73
74
|
includeIgnoreIfExists(path.join(baseDirectory, '.gitignore'), `Ignore rules from .gitignore`),
|
|
74
75
|
includeIgnoreIfExists(path.join(baseDirectory, '.lintignore'), `Ignore rules from .lintignore`),
|
|
75
76
|
createJsonConfig(),
|
|
77
|
+
createPackageJsonConfig(),
|
|
76
78
|
prettier,
|
|
77
79
|
// Markdown + MDX linting via eslint-plugin-mdx. Severities for markdown
|
|
78
80
|
// quality checks live in the project's `.remarkrc` (see
|
|
@@ -19,17 +19,35 @@ export function createDocsConfig() {
|
|
|
19
19
|
? nextjsAlias.flatConfig.recommended
|
|
20
20
|
: nextjsAlias.configs.recommended;
|
|
21
21
|
|
|
22
|
-
return defineConfig(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
return defineConfig(
|
|
23
|
+
recommendedConfig,
|
|
24
|
+
{
|
|
25
|
+
settings: {
|
|
26
|
+
next: {
|
|
27
|
+
rootDir: 'docs',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
files: [`**/*${EXTENSION_TS}`],
|
|
31
|
+
rules: {
|
|
32
|
+
'compat/compat': 'off',
|
|
33
|
+
'jsx-a11y/anchor-is-valid': 'off',
|
|
34
|
+
'no-irregular-whitespace': ['error', { skipJSXText: true, skipStrings: true }],
|
|
26
35
|
},
|
|
27
36
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
{
|
|
38
|
+
// Build configuration and maintenance scripts aren't documentation examples
|
|
39
|
+
// and are often intentionally outside the docs TypeScript project.
|
|
40
|
+
files: [`**/*${EXTENSION_TS}`],
|
|
41
|
+
ignores: ['**/*.config.*', '**/scripts/**'],
|
|
42
|
+
languageOptions: {
|
|
43
|
+
parserOptions: {
|
|
44
|
+
projectService: true,
|
|
45
|
+
tsconfigRootDir: process.cwd(),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
rules: {
|
|
49
|
+
'@typescript-eslint/no-deprecated': 'error',
|
|
50
|
+
},
|
|
33
51
|
},
|
|
34
|
-
|
|
52
|
+
);
|
|
35
53
|
}
|
|
@@ -9,7 +9,8 @@ export function createJsonConfig() {
|
|
|
9
9
|
{
|
|
10
10
|
name: 'JSON files',
|
|
11
11
|
files: ['**/*.json'],
|
|
12
|
-
|
|
12
|
+
// package.json is linted by eslint-plugin-package-json, whose rules need the jsonc-eslint-parser AST.
|
|
13
|
+
ignores: ['package-lock.json', '**/package.json'],
|
|
13
14
|
plugins: { json },
|
|
14
15
|
language: 'json/json',
|
|
15
16
|
extends: [json.configs.recommended],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineConfig } from 'eslint/config';
|
|
2
|
-
import { EXTENSION_DTS } from '../extensions.mjs';
|
|
2
|
+
import { EXTENSION_DTS, EXTENSION_TS_ONLY } from '../extensions.mjs';
|
|
3
3
|
|
|
4
4
|
const restrictedMethods = ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'];
|
|
5
5
|
|
|
@@ -184,6 +184,14 @@ const typescriptOverrides = {
|
|
|
184
184
|
// TypeScript equivalents of ESLint rules
|
|
185
185
|
'default-param-last': 'off',
|
|
186
186
|
'@typescript-eslint/default-param-last': 'error',
|
|
187
|
+
'@typescript-eslint/ban-ts-comment': [
|
|
188
|
+
'error',
|
|
189
|
+
{
|
|
190
|
+
'ts-expect-error': 'allow-with-description',
|
|
191
|
+
'ts-ignore': true,
|
|
192
|
+
minimumDescriptionLength: 10,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
187
195
|
'no-array-constructor': 'off',
|
|
188
196
|
'@typescript-eslint/no-array-constructor': 'error',
|
|
189
197
|
'@typescript-eslint/triple-slash-reference': 'off',
|
|
@@ -538,6 +546,13 @@ export function createCoreConfig(options = {}) {
|
|
|
538
546
|
'@typescript-eslint/return-await': 'off',
|
|
539
547
|
},
|
|
540
548
|
},
|
|
549
|
+
{
|
|
550
|
+
name: 'mui-base/ts',
|
|
551
|
+
files: [`**/*${EXTENSION_TS_ONLY}`],
|
|
552
|
+
rules: {
|
|
553
|
+
'mui/no-constructor-assigned-field': 'error',
|
|
554
|
+
},
|
|
555
|
+
},
|
|
541
556
|
{
|
|
542
557
|
name: 'mui-base/dts',
|
|
543
558
|
files: [`**/*${EXTENSION_DTS}`],
|
package/src/eslint/mui/index.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import addUndefToOptional from './rules/add-undef-to-optional.mjs';
|
|
|
14
14
|
import flattenParentheses from './rules/flatten-parentheses.mjs';
|
|
15
15
|
import noPresentationRole from './rules/no-presentation-role.mjs';
|
|
16
16
|
import noFloatingCleanup from './rules/no-floating-cleanup.mjs';
|
|
17
|
+
import noConstructorAssignedField from './rules/no-constructor-assigned-field.mjs';
|
|
17
18
|
|
|
18
19
|
/** @type {import('eslint').ESLint.Plugin} */
|
|
19
20
|
const muiPlugin = {
|
|
@@ -39,6 +40,7 @@ const muiPlugin = {
|
|
|
39
40
|
'flatten-parentheses': /** @type {any} */ (flattenParentheses),
|
|
40
41
|
'no-presentation-role': noPresentationRole,
|
|
41
42
|
'no-floating-cleanup': /** @type {any} */ (noFloatingCleanup),
|
|
43
|
+
'no-constructor-assigned-field': /** @type {any} */ (noConstructorAssignedField),
|
|
42
44
|
},
|
|
43
45
|
};
|
|
44
46
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { ASTUtils, ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('@typescript-eslint/utils').TSESTree.PropertyDefinition} PropertyDefinition
|
|
5
|
+
* @typedef {import('@typescript-eslint/utils').TSESTree.Node} Node
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
9
|
+
(name) =>
|
|
10
|
+
`https://github.com/mui/mui-public/blob/master/packages/code-infra/src/eslint/mui/rules/${name}.mjs`,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the constructor whose own code contains `node`, or `null` when `node` is outside a
|
|
15
|
+
* constructor or inside a nested function or class.
|
|
16
|
+
* @param {Node} node
|
|
17
|
+
*/
|
|
18
|
+
function getEnclosingConstructor(node) {
|
|
19
|
+
let current = node.parent;
|
|
20
|
+
while (current && !ASTUtils.isFunction(current)) {
|
|
21
|
+
if (current.type === 'ClassBody') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
current = current.parent;
|
|
25
|
+
}
|
|
26
|
+
const method = current?.parent;
|
|
27
|
+
return method && ASTUtils.isConstructor(method) ? method : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the extra edits needed before `field` can be marked `declare`.
|
|
32
|
+
* @param {PropertyDefinition} field
|
|
33
|
+
*/
|
|
34
|
+
function getDeclareHint(field) {
|
|
35
|
+
const edits = [];
|
|
36
|
+
if (field.value) {
|
|
37
|
+
edits.push('drop its initializer');
|
|
38
|
+
}
|
|
39
|
+
if (field.definite) {
|
|
40
|
+
edits.push('drop the `!`');
|
|
41
|
+
}
|
|
42
|
+
if (field.override) {
|
|
43
|
+
edits.push('drop `override`');
|
|
44
|
+
}
|
|
45
|
+
return edits.length > 0 ? ` (${edits.join(', ')})` : '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ESLint rule that disallows assigning a class field in the constructor when it is also
|
|
50
|
+
* declared as a (non-`declare`) class field.
|
|
51
|
+
*
|
|
52
|
+
* Babel 8 only strips TypeScript fields marked `declare` (see mui/material-ui#49241), so a
|
|
53
|
+
* field without an initializer is emitted as a property definition that sets it to
|
|
54
|
+
* `undefined` before the constructor assigns it again. Either initialize the field at its
|
|
55
|
+
* declaration, or mark it `declare` so it only serves as a type annotation.
|
|
56
|
+
*
|
|
57
|
+
* `declare` fields are exempt from TypeScript's strict property initialization check
|
|
58
|
+
* (TS2564), so prefer initializing at the declaration where the value doesn't depend on
|
|
59
|
+
* constructor arguments. The `declare` suggestion is only offered when every path through the
|
|
60
|
+
* constructor assigns the field, i.e. for an unconditional top-level assignment.
|
|
61
|
+
*
|
|
62
|
+
* Only plain `=` assignments count: compound assignments read the field rather than
|
|
63
|
+
* re-initialize it. A field with an initializer is only reported for an unconditional
|
|
64
|
+
* top-level assignment, as a conditional one overrides a default. Private (`#`) and decorated
|
|
65
|
+
* fields are ignored: they are runtime declarations that `declare` can't replace.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Invalid
|
|
69
|
+
* class ModalManager {
|
|
70
|
+
* private modals: Modal[];
|
|
71
|
+
* constructor() {
|
|
72
|
+
* this.modals = [];
|
|
73
|
+
* }
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Valid - initialized at the declaration
|
|
78
|
+
* class ModalManager {
|
|
79
|
+
* private modals: Modal[] = [];
|
|
80
|
+
* }
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // Valid - declared only, assigned in the constructor
|
|
84
|
+
* class ModalManager {
|
|
85
|
+
* declare private modals: Modal[];
|
|
86
|
+
* constructor(modals: Modal[]) {
|
|
87
|
+
* this.modals = modals;
|
|
88
|
+
* }
|
|
89
|
+
* }
|
|
90
|
+
*/
|
|
91
|
+
export default createRule({
|
|
92
|
+
name: 'no-constructor-assigned-field',
|
|
93
|
+
meta: {
|
|
94
|
+
type: 'suggestion',
|
|
95
|
+
docs: {
|
|
96
|
+
description:
|
|
97
|
+
'Disallow assigning class fields in the constructor when they are also declared as class fields',
|
|
98
|
+
},
|
|
99
|
+
hasSuggestions: true,
|
|
100
|
+
messages: {
|
|
101
|
+
assignedInConstructor:
|
|
102
|
+
"Field '{{name}}' is declared as a class field and also assigned in the constructor. " +
|
|
103
|
+
'Either initialize it at the declaration and remove the constructor assignment, ' +
|
|
104
|
+
'or mark it `declare`{{declareHint}}.',
|
|
105
|
+
addDeclare: 'Mark the field `declare`.',
|
|
106
|
+
},
|
|
107
|
+
schema: [],
|
|
108
|
+
},
|
|
109
|
+
defaultOptions: [],
|
|
110
|
+
create(context) {
|
|
111
|
+
/**
|
|
112
|
+
* Fields assigned in a constructor, mapped to whether any of those assignments is an
|
|
113
|
+
* unconditional top-level statement.
|
|
114
|
+
* @type {Map<PropertyDefinition, boolean>}
|
|
115
|
+
*/
|
|
116
|
+
const assignedFields = new Map();
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
AssignmentExpression(node) {
|
|
120
|
+
if (
|
|
121
|
+
node.operator !== '=' ||
|
|
122
|
+
node.left.type !== 'MemberExpression' ||
|
|
123
|
+
node.left.object.type !== 'ThisExpression'
|
|
124
|
+
) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const name = ASTUtils.getPropertyName(node.left);
|
|
128
|
+
if (name === null) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const constructor = getEnclosingConstructor(node);
|
|
132
|
+
if (!constructor) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const field = constructor.parent.body.find(
|
|
137
|
+
/** @returns {member is PropertyDefinition} */
|
|
138
|
+
(member) =>
|
|
139
|
+
member.type === 'PropertyDefinition' &&
|
|
140
|
+
!member.static &&
|
|
141
|
+
!member.declare &&
|
|
142
|
+
member.decorators.length === 0 &&
|
|
143
|
+
ASTUtils.getPropertyName(member) === name,
|
|
144
|
+
);
|
|
145
|
+
if (!field) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const statement = node.parent;
|
|
150
|
+
const unconditional =
|
|
151
|
+
statement.type === 'ExpressionStatement' && statement.parent === constructor.value.body;
|
|
152
|
+
assignedFields.set(field, Boolean(assignedFields.get(field)) || unconditional);
|
|
153
|
+
},
|
|
154
|
+
'ClassBody:exit'(classBody) {
|
|
155
|
+
for (const member of classBody.body) {
|
|
156
|
+
if (member.type !== 'PropertyDefinition' || !assignedFields.has(member)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const unconditional = Boolean(assignedFields.get(member));
|
|
160
|
+
if (member.value && !unconditional) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const declareHint = getDeclareHint(member);
|
|
165
|
+
context.report({
|
|
166
|
+
node: member,
|
|
167
|
+
messageId: 'assignedInConstructor',
|
|
168
|
+
data: { name: ASTUtils.getPropertyName(member), declareHint },
|
|
169
|
+
suggest:
|
|
170
|
+
unconditional && declareHint === ''
|
|
171
|
+
? [
|
|
172
|
+
{
|
|
173
|
+
messageId: 'addDeclare',
|
|
174
|
+
fix: (fixer) => fixer.insertTextBefore(member, 'declare '),
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
: [],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineConfig } from 'eslint/config';
|
|
2
|
+
import packageJson from 'eslint-plugin-package-json';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lints package.json files for publishing metadata. Published packages must
|
|
6
|
+
* declare an SPDX license, `"author": "MUI Team"` and a repository pointing at
|
|
7
|
+
* their own directory. Private packages must not carry publishing metadata.
|
|
8
|
+
* @returns {import('eslint').Linter.Config[]}
|
|
9
|
+
*/
|
|
10
|
+
export function createPackageJsonConfig() {
|
|
11
|
+
return defineConfig([
|
|
12
|
+
{
|
|
13
|
+
name: 'package.json files',
|
|
14
|
+
files: ['**/package.json'],
|
|
15
|
+
ignores: ['**/__fixtures__/**'],
|
|
16
|
+
extends: [packageJson.configs.recommended],
|
|
17
|
+
rules: {
|
|
18
|
+
'package-json/require-author': ['error', { ignorePrivate: true }],
|
|
19
|
+
'package-json/require-description': ['error', { ignorePrivate: true }],
|
|
20
|
+
'package-json/restrict-private-properties': [
|
|
21
|
+
'error',
|
|
22
|
+
// `publishConfig.directory` is allowed: `code-infra build` reads it, also for private packages.
|
|
23
|
+
{ blockedProperties: ['files', 'license'] },
|
|
24
|
+
],
|
|
25
|
+
'no-restricted-syntax': [
|
|
26
|
+
'error',
|
|
27
|
+
{
|
|
28
|
+
selector:
|
|
29
|
+
'Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value="author"][value.value!="MUI Team"]',
|
|
30
|
+
message: 'The "author" field must be "MUI Team".',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
// Condition order in "exports" is significant, and scripts are grouped by purpose.
|
|
34
|
+
'package-json/sort-collections': 'off',
|
|
35
|
+
// Packages built with `code-infra build` get these fields in the generated package.json.
|
|
36
|
+
'package-json/require-files': 'off',
|
|
37
|
+
'package-json/require-sideEffects': 'off',
|
|
38
|
+
'package-json/require-type': 'off',
|
|
39
|
+
// Peer dependencies are installed once at the workspace root.
|
|
40
|
+
'package-json/specify-peers-locally': 'off',
|
|
41
|
+
// CLI-only packages have no "exports".
|
|
42
|
+
'package-json/require-exports': 'off',
|
|
43
|
+
// Rejects the object form of "browser", which the browser field spec allows.
|
|
44
|
+
'package-json/valid-browser': 'off',
|
|
45
|
+
// npm only auto-includes "main" when it has no "./" prefix; removing it from "files" can drop the entry point.
|
|
46
|
+
'package-json/no-redundant-files': 'off',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
@@ -21,7 +21,6 @@ export const baseSpecRules = {
|
|
|
21
21
|
'@typescript-eslint/no-unused-expressions': 'off',
|
|
22
22
|
'@typescript-eslint/no-unused-vars': 'off',
|
|
23
23
|
'@typescript-eslint/no-use-before-define': 'off',
|
|
24
|
-
'@typescript-eslint/ban-ts-comment': 'off',
|
|
25
24
|
|
|
26
25
|
'import/prefer-default-export': 'off',
|
|
27
26
|
|
|
@@ -63,6 +62,7 @@ export function createTestConfig() {
|
|
|
63
62
|
'import/named': 'off',
|
|
64
63
|
'mui/disallow-active-element-as-key-event-target': 'error',
|
|
65
64
|
'mui/consistent-production-guard': 'off',
|
|
65
|
+
'mui/no-constructor-assigned-field': 'off',
|
|
66
66
|
|
|
67
67
|
// disable eslint-plugin-jsx-a11y
|
|
68
68
|
// tests are not driven by assistive technology
|
|
@@ -87,7 +87,6 @@ export function createTestConfig() {
|
|
|
87
87
|
'react/no-unused-prop-types': 'off',
|
|
88
88
|
// Part of the migration away from airbnb config. Turned of initially.
|
|
89
89
|
'@typescript-eslint/no-empty-function': 'off',
|
|
90
|
-
'@typescript-eslint/ban-ts-comment': 'off',
|
|
91
90
|
'testing-library/no-node-access': 'off',
|
|
92
91
|
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
|
|
93
92
|
// end migration
|
package/src/remark/config.mjs
CHANGED
|
@@ -97,6 +97,49 @@ function withOverrides(name, plugin, baseSettings, overrideEntries) {
|
|
|
97
97
|
return wrapper;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Allows link-reference comments while retaining diagnostics for real links and definitions.
|
|
102
|
+
* Existing docs generators use these markers in both Markdown and MDX and parse them as metadata.
|
|
103
|
+
* Authored MDX can use JSX comments without this compatibility option.
|
|
104
|
+
*
|
|
105
|
+
* @param {import('unified').Plugin<any[], any, any>} plugin
|
|
106
|
+
*/
|
|
107
|
+
function withDefinitionComments(plugin) {
|
|
108
|
+
/** @param {any[]} settings */
|
|
109
|
+
return function wrapper(...settings) {
|
|
110
|
+
const processor = unified().use(plugin, ...settings);
|
|
111
|
+
/** @type {import('unified').Transformer} */
|
|
112
|
+
return async function transformer(tree, file) {
|
|
113
|
+
const previousMessages = file.messages.length;
|
|
114
|
+
await processor.run(tree, file);
|
|
115
|
+
file.messages = file.messages.filter((message, index) => {
|
|
116
|
+
const node = message.ancestors?.at(-1);
|
|
117
|
+
return !(
|
|
118
|
+
index >= previousMessages &&
|
|
119
|
+
node?.type === 'definition' &&
|
|
120
|
+
'identifier' in node &&
|
|
121
|
+
node.identifier === '//' &&
|
|
122
|
+
'title' in node &&
|
|
123
|
+
typeof node.title === 'string' &&
|
|
124
|
+
node.title.length > 0
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Makes the processor output the source text unchanged. The preset only lints, and
|
|
133
|
+
* remark-stringify doesn't round-trip Markdown (it escapes GitHub alerts, rewrites bare
|
|
134
|
+
* URLs, ...). eslint-plugin-mdx offers the processed output as a whole-file fix for any
|
|
135
|
+
* remark message, so with this `eslint --fix` leaves Markdown alone.
|
|
136
|
+
*
|
|
137
|
+
* @this {import('unified').Processor}
|
|
138
|
+
*/
|
|
139
|
+
function remarkPreserveSource() {
|
|
140
|
+
this.compiler = (_tree, file) => String(file);
|
|
141
|
+
}
|
|
142
|
+
|
|
100
143
|
/**
|
|
101
144
|
* Returns a remark preset wiring the MUI-authored remark-lint plugins together
|
|
102
145
|
* with a curated set of community plugins. Drop this into `.remarkrc.mjs`:
|
|
@@ -120,9 +163,11 @@ function withOverrides(name, plugin, baseSettings, overrideEntries) {
|
|
|
120
163
|
* ```
|
|
121
164
|
*
|
|
122
165
|
* @param {Object} [options]
|
|
166
|
+
* @param {boolean} [options.allowDefinitionComments=false] Allow titled `[//]` link-reference comments.
|
|
123
167
|
* @param {Array<{ files: string | string[], rules: Record<string, false | unknown[]> }>} [options.overrides]
|
|
168
|
+
* @returns {import('unified').Preset}
|
|
124
169
|
*/
|
|
125
|
-
export function createRemarkConfig({ overrides = [] } = {}) {
|
|
170
|
+
export function createRemarkConfig({ overrides = [], allowDefinitionComments = false } = {}) {
|
|
126
171
|
for (const override of overrides) {
|
|
127
172
|
const unknown = Object.keys(override.rules).filter((ruleName) => !(ruleName in RULES));
|
|
128
173
|
if (unknown.length > 0) {
|
|
@@ -130,10 +175,14 @@ export function createRemarkConfig({ overrides = [] } = {}) {
|
|
|
130
175
|
}
|
|
131
176
|
}
|
|
132
177
|
|
|
178
|
+
/** @type {import('unified').PluggableList} */
|
|
133
179
|
const entries = Object.entries(RULES).map(([name, entry]) => {
|
|
134
|
-
const [
|
|
135
|
-
entry
|
|
136
|
-
|
|
180
|
+
const [rulePlugin, baseSettings] =
|
|
181
|
+
/** @type {[import('unified').Plugin<any[], any, any>, any]} */ (entry);
|
|
182
|
+
const plugin =
|
|
183
|
+
allowDefinitionComments && (name === 'no-empty-url' || name === 'no-unused-definitions')
|
|
184
|
+
? withDefinitionComments(rulePlugin)
|
|
185
|
+
: rulePlugin;
|
|
137
186
|
const overrideEntries = overrides
|
|
138
187
|
.filter((override) => name in override.rules)
|
|
139
188
|
.map((override) => ({ files: override.files, settings: override.rules[name] }));
|
|
@@ -144,14 +193,12 @@ export function createRemarkConfig({ overrides = [] } = {}) {
|
|
|
144
193
|
});
|
|
145
194
|
|
|
146
195
|
return {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
},
|
|
155
|
-
plugins: [[remarkFrontmatter, ['yaml', 'toml']], remarkGfm, remarkLint, ...entries],
|
|
196
|
+
plugins: [
|
|
197
|
+
[remarkFrontmatter, ['yaml', 'toml']],
|
|
198
|
+
remarkGfm,
|
|
199
|
+
remarkLint,
|
|
200
|
+
...entries,
|
|
201
|
+
remarkPreserveSource,
|
|
202
|
+
],
|
|
156
203
|
};
|
|
157
204
|
}
|
package/src/utils/build.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import getTargets from '@babel/helper-compilation-targets';
|
|
3
4
|
import { globby } from 'globby';
|
|
4
5
|
import * as semver from 'semver';
|
|
5
6
|
|
|
@@ -867,6 +868,39 @@ export function validatePkgJson(packageJson, options = {}) {
|
|
|
867
868
|
}
|
|
868
869
|
}
|
|
869
870
|
|
|
871
|
+
/**
|
|
872
|
+
* Validates that "engines.node" in package.json doesn't allow Node.js versions older than the
|
|
873
|
+
* "node" browserslist environment, which the CommonJS bundle is compiled for. Otherwise the
|
|
874
|
+
* build can emit syntax that the oldest supported Node.js version can't run.
|
|
875
|
+
* Targets are resolved the same way `@babel/preset-env` resolves them during the build.
|
|
876
|
+
* @param {{ name?: string; engines?: { node?: string } }} packageJson
|
|
877
|
+
* @param {string} cwd - The package directory, used to find the browserslist config.
|
|
878
|
+
*/
|
|
879
|
+
export function validateEnginesNode(packageJson, cwd) {
|
|
880
|
+
const enginesNode = packageJson.engines?.node;
|
|
881
|
+
if (!enginesNode) {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const minEnginesVersion = semver.minVersion(enginesNode);
|
|
886
|
+
if (!minEnginesVersion) {
|
|
887
|
+
throw new Error(
|
|
888
|
+
`Unable to determine the minimum Node.js version from "engines.node" in "${packageJson.name}" package.json: "${enginesNode}".`,
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const targetNode = getTargets(undefined, { configPath: cwd, browserslistEnv: 'node' }).node;
|
|
893
|
+
if (!targetNode) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (semver.gt(targetNode, minEnginesVersion)) {
|
|
898
|
+
throw new Error(
|
|
899
|
+
`"engines.node" in "${packageJson.name}" package.json allows Node.js ${minEnginesVersion.version}, but the "node" browserslist environment compiles for Node.js ${targetNode} and up. Raise "engines.node" to at least ${targetNode}, or lower the "node" browserslist target.`,
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
870
904
|
/**
|
|
871
905
|
* Marks the start and end of a function execution for performance measurement.
|
|
872
906
|
* Uses the Performance API to create marks and measure the duration.
|
|
@@ -23,11 +23,12 @@ const accessedCredentials = new Map();
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @param {string} key
|
|
26
|
-
* @returns {Promise<string |
|
|
26
|
+
* @returns {Promise<string | null>}
|
|
27
27
|
*/
|
|
28
28
|
export async function getPassword(key) {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const cached = accessedCredentials.get(key);
|
|
30
|
+
if (cached) {
|
|
31
|
+
return cached;
|
|
31
32
|
}
|
|
32
33
|
let credential = credentials.get(key);
|
|
33
34
|
if (!credential) {
|
|
@@ -99,9 +99,7 @@ export default async function extractErrorCodes(args) {
|
|
|
99
99
|
const packages = basePackages.filter(
|
|
100
100
|
(pkg) =>
|
|
101
101
|
// Ignore obvious packages that do not have user-facing errors
|
|
102
|
-
!pkg.name.startsWith('@mui/internal-') &&
|
|
103
|
-
!pkg.name.startsWith('@mui-internal/') &&
|
|
104
|
-
!skipPackages.includes(pkg.name),
|
|
102
|
+
!pkg.name.startsWith('@mui/internal-') && !skipPackages.includes(pkg.name),
|
|
105
103
|
);
|
|
106
104
|
const files = await Promise.all(packages.map((pkg) => getFilesForPackage(pkg)));
|
|
107
105
|
packages.forEach((pkg, index) => {
|