@mui/internal-code-infra 0.0.3-canary.87 → 0.0.3-canary.89
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/build/eslint/mui/rules/flatten-parentheses.d.mts +5 -0
- package/package.json +2 -2
- package/src/cli/cmdPublishNewPackage.mjs +14 -0
- package/src/eslint/mui/config.mjs +1 -0
- package/src/eslint/mui/index.mjs +2 -0
- package/src/eslint/mui/rules/flatten-parentheses.mjs +84 -0
- package/src/eslint/mui/rules/flatten-parentheses.test.mjs +234 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-code-infra",
|
|
3
|
-
"version": "0.0.3-canary.
|
|
3
|
+
"version": "0.0.3-canary.89",
|
|
4
4
|
"description": "Infra scripts and configs to be used across MUI repos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"publishConfig": {
|
|
153
153
|
"access": "public"
|
|
154
154
|
},
|
|
155
|
-
"gitSha": "
|
|
155
|
+
"gitSha": "67eca269ee332f573b258735ece9f448874e2468",
|
|
156
156
|
"scripts": {
|
|
157
157
|
"build": "tsc -p tsconfig.build.json",
|
|
158
158
|
"typescript": "tsc -p tsconfig.json",
|
|
@@ -7,6 +7,9 @@ import fs from 'node:fs/promises';
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
|
|
10
|
+
import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
|
|
11
|
+
|
|
12
|
+
import { getRepositoryInfo } from '../utils/git.mjs';
|
|
10
13
|
import { getWorkspacePackages } from '../utils/pnpm.mjs';
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -31,6 +34,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
31
34
|
console.log('No new packages to publish.');
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
37
|
+
const cwd = process.cwd();
|
|
34
38
|
|
|
35
39
|
console.log(`Found ${newPackages.map((pkg) => pkg.name).join(', ')} to publish.`);
|
|
36
40
|
|
|
@@ -42,14 +46,24 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
42
46
|
return;
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
const workspaceDir = await findWorkspaceDir(cwd);
|
|
50
|
+
if (!workspaceDir) {
|
|
51
|
+
throw new Error('This command should be run in a workspace.');
|
|
52
|
+
}
|
|
45
53
|
await Promise.all(
|
|
46
54
|
newPackages.map(async (pkg) => {
|
|
47
55
|
const newPkgDir = await fs.mkdtemp(path.join(os.tmpdir(), 'publish-new-package-'));
|
|
48
56
|
try {
|
|
49
57
|
await fs.mkdir(newPkgDir, { recursive: true });
|
|
58
|
+
const repo = await getRepositoryInfo();
|
|
50
59
|
const packageJson = {
|
|
51
60
|
name: pkg.name,
|
|
52
61
|
version: '0.0.1',
|
|
62
|
+
repository: {
|
|
63
|
+
type: 'git',
|
|
64
|
+
url: `git+https://github.com/${repo.owner}/${repo.remoteName}.git`,
|
|
65
|
+
directory: path.relative(workspaceDir, pkg.path).split(path.sep).join('/'),
|
|
66
|
+
},
|
|
53
67
|
};
|
|
54
68
|
await fs.writeFile(
|
|
55
69
|
path.join(newPkgDir, 'package.json'),
|
|
@@ -417,6 +417,7 @@ export function createCoreConfig(options = {}) {
|
|
|
417
417
|
'mui/straight-quotes': 'off',
|
|
418
418
|
'mui/consistent-production-guard': 'error',
|
|
419
419
|
'mui/add-undef-to-optional': 'off',
|
|
420
|
+
'mui/flatten-parentheses': 'warn',
|
|
420
421
|
|
|
421
422
|
'react-hooks/exhaustive-deps': [
|
|
422
423
|
'error',
|
package/src/eslint/mui/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import requireDevWrapper from './rules/require-dev-wrapper.mjs';
|
|
|
10
10
|
import rulesOfUseThemeVariants from './rules/rules-of-use-theme-variants.mjs';
|
|
11
11
|
import straightQuotes from './rules/straight-quotes.mjs';
|
|
12
12
|
import addUndefToOptional from './rules/add-undef-to-optional.mjs';
|
|
13
|
+
import flattenParentheses from './rules/flatten-parentheses.mjs';
|
|
13
14
|
|
|
14
15
|
export default /** @type {import('eslint').ESLint.Plugin} */ ({
|
|
15
16
|
meta: {
|
|
@@ -30,5 +31,6 @@ export default /** @type {import('eslint').ESLint.Plugin} */ ({
|
|
|
30
31
|
'require-dev-wrapper': requireDevWrapper,
|
|
31
32
|
// Some discrepancies between TypeScript and ESLint types - casting to unknown
|
|
32
33
|
'add-undef-to-optional': /** @type {unknown} */ (addUndefToOptional),
|
|
34
|
+
'flatten-parentheses': /** @type {unknown} */ (flattenParentheses),
|
|
33
35
|
},
|
|
34
36
|
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
4
|
+
(name) =>
|
|
5
|
+
`https://github.com/mui/mui-public/blob/master/packages/code-infra/src/eslint/mui/rules/${name}.mjs`,
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns the source range including surrounding parentheses, or null if the node is not parenthesized.
|
|
10
|
+
* @param {import('@typescript-eslint/types').TSESTree.Node} node
|
|
11
|
+
* @param {import('@typescript-eslint/utils').TSESLint.SourceCode} sourceCode
|
|
12
|
+
* @returns {[number, number] | null}
|
|
13
|
+
*/
|
|
14
|
+
function getParenthesizedRange(node, sourceCode) {
|
|
15
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
|
16
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
tokenBefore?.value === '(' &&
|
|
20
|
+
tokenBefore?.type === 'Punctuator' &&
|
|
21
|
+
tokenAfter?.value === ')' &&
|
|
22
|
+
tokenAfter?.type === 'Punctuator'
|
|
23
|
+
) {
|
|
24
|
+
return [tokenBefore.range[0], tokenAfter.range[1]];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default createRule({
|
|
31
|
+
meta: {
|
|
32
|
+
docs: {
|
|
33
|
+
description:
|
|
34
|
+
'Flatten unnecessary parentheses in TypeScript unions and intersections when safe to do so. NOTE: This rule will become obsolete once Prettier handles this formatting automatically (see https://github.com/prettier/prettier/issues/13500).',
|
|
35
|
+
},
|
|
36
|
+
messages: {
|
|
37
|
+
flattenParentheses:
|
|
38
|
+
'Unnecessary parentheses in {{ operatorType }}. The inner types can be flattened.',
|
|
39
|
+
},
|
|
40
|
+
type: 'suggestion',
|
|
41
|
+
fixable: 'code',
|
|
42
|
+
schema: [],
|
|
43
|
+
},
|
|
44
|
+
name: 'flatten-parentheses',
|
|
45
|
+
defaultOptions: [],
|
|
46
|
+
create(context) {
|
|
47
|
+
const sourceCode = context.sourceCode;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {import('@typescript-eslint/types').TSESTree.TSUnionType | import('@typescript-eslint/types').TSESTree.TSIntersectionType} node
|
|
51
|
+
*/
|
|
52
|
+
function checkNode(node) {
|
|
53
|
+
const operatorType = node.type === AST_NODE_TYPES.TSUnionType ? 'union' : 'intersection';
|
|
54
|
+
|
|
55
|
+
for (const typeNode of node.types) {
|
|
56
|
+
// Only flatten when the child operator matches the parent (union-in-union or intersection-in-intersection)
|
|
57
|
+
if (typeNode.type !== node.type) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const range = getParenthesizedRange(typeNode, sourceCode);
|
|
62
|
+
if (!range) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
context.report({
|
|
67
|
+
node: typeNode,
|
|
68
|
+
messageId: 'flattenParentheses',
|
|
69
|
+
data: { operatorType },
|
|
70
|
+
fix(fixer) {
|
|
71
|
+
// Use text between parens (exclusive) to preserve any interleaved comments
|
|
72
|
+
const innerText = sourceCode.text.slice(range[0] + 1, range[1] - 1).trimStart();
|
|
73
|
+
return fixer.replaceTextRange(range, innerText);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
TSUnionType: checkNode,
|
|
81
|
+
TSIntersectionType: checkNode,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
+
import TSESlintParser from '@typescript-eslint/parser';
|
|
3
|
+
import rule from './flatten-parentheses.mjs';
|
|
4
|
+
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
parser: TSESlintParser,
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
ruleTester.run('flatten-parentheses', rule, {
|
|
12
|
+
valid: [
|
|
13
|
+
// Simple union without parentheses
|
|
14
|
+
`type T = 1 | 2 | 3;`,
|
|
15
|
+
|
|
16
|
+
// Simple intersection without parentheses
|
|
17
|
+
`type T = A & B & C;`,
|
|
18
|
+
|
|
19
|
+
// Parentheses around single type (not in union/intersection context)
|
|
20
|
+
`type T = (string);`,
|
|
21
|
+
|
|
22
|
+
// Parentheses needed for precedence (intersection inside union)
|
|
23
|
+
`type T = (A & B) | C;`,
|
|
24
|
+
|
|
25
|
+
// Parentheses needed for precedence (union inside intersection)
|
|
26
|
+
`type T = (A | B) & C;`,
|
|
27
|
+
|
|
28
|
+
// Complex nested structure where parentheses change meaning
|
|
29
|
+
`type T = (A | B) & (C | D);`,
|
|
30
|
+
|
|
31
|
+
// Function types where parentheses are needed
|
|
32
|
+
`type T = (() => void) | string;`,
|
|
33
|
+
|
|
34
|
+
// Array types
|
|
35
|
+
`type T = (string | number)[];`,
|
|
36
|
+
|
|
37
|
+
// Generic types
|
|
38
|
+
`type T = Promise<string | number>;`,
|
|
39
|
+
|
|
40
|
+
// Mixed operators - parentheses are needed
|
|
41
|
+
`type T = A | (B & C);`,
|
|
42
|
+
`type T = (A | B) & (C | D) | E;`,
|
|
43
|
+
],
|
|
44
|
+
invalid: [
|
|
45
|
+
// Basic union flattening - example from the problem statement
|
|
46
|
+
{
|
|
47
|
+
code: `type T = (1 | 2 | 3) | 4;`,
|
|
48
|
+
output: `type T = 1 | 2 | 3 | 4;`,
|
|
49
|
+
errors: [
|
|
50
|
+
{
|
|
51
|
+
messageId: 'flattenParentheses',
|
|
52
|
+
line: 1,
|
|
53
|
+
column: 11,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Union on the right side
|
|
59
|
+
{
|
|
60
|
+
code: `type T = 1 | (2 | 3 | 4);`,
|
|
61
|
+
output: `type T = 1 | 2 | 3 | 4;`,
|
|
62
|
+
errors: [
|
|
63
|
+
{
|
|
64
|
+
messageId: 'flattenParentheses',
|
|
65
|
+
line: 1,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Multiple parenthesized unions
|
|
71
|
+
{
|
|
72
|
+
code: `type T = (1 | 2) | (3 | 4);`,
|
|
73
|
+
output: `type T = 1 | 2 | 3 | 4;`,
|
|
74
|
+
errors: [
|
|
75
|
+
{
|
|
76
|
+
messageId: 'flattenParentheses',
|
|
77
|
+
line: 1,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
messageId: 'flattenParentheses',
|
|
81
|
+
line: 1,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// Intersection flattening
|
|
87
|
+
{
|
|
88
|
+
code: `type T = (A & B & C) & D;`,
|
|
89
|
+
output: `type T = A & B & C & D;`,
|
|
90
|
+
errors: [
|
|
91
|
+
{
|
|
92
|
+
messageId: 'flattenParentheses',
|
|
93
|
+
line: 1,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Intersection on the right
|
|
99
|
+
{
|
|
100
|
+
code: `type T = A & (B & C & D);`,
|
|
101
|
+
output: `type T = A & B & C & D;`,
|
|
102
|
+
errors: [
|
|
103
|
+
{
|
|
104
|
+
messageId: 'flattenParentheses',
|
|
105
|
+
line: 1,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// Multiple parenthesized intersections
|
|
111
|
+
{
|
|
112
|
+
code: `type T = (A & B) & (C & D);`,
|
|
113
|
+
output: `type T = A & B & C & D;`,
|
|
114
|
+
errors: [
|
|
115
|
+
{
|
|
116
|
+
messageId: 'flattenParentheses',
|
|
117
|
+
line: 1,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
messageId: 'flattenParentheses',
|
|
121
|
+
line: 1,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// With type aliases
|
|
127
|
+
{
|
|
128
|
+
code: `type Union = (string | number) | boolean;`,
|
|
129
|
+
output: `type Union = string | number | boolean;`,
|
|
130
|
+
errors: [
|
|
131
|
+
{
|
|
132
|
+
messageId: 'flattenParentheses',
|
|
133
|
+
line: 1,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// In type parameters
|
|
139
|
+
{
|
|
140
|
+
code: `type T<U> = (U | null) | undefined;`,
|
|
141
|
+
output: `type T<U> = U | null | undefined;`,
|
|
142
|
+
errors: [
|
|
143
|
+
{
|
|
144
|
+
messageId: 'flattenParentheses',
|
|
145
|
+
line: 1,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// With whitespace (trailing space left for Prettier to clean up)
|
|
151
|
+
{
|
|
152
|
+
code: `type T = ( A | B ) | C;`,
|
|
153
|
+
output: `type T = A | B | C;`,
|
|
154
|
+
errors: [
|
|
155
|
+
{
|
|
156
|
+
messageId: 'flattenParentheses',
|
|
157
|
+
line: 1,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// Nested unions - multiple fix passes needed due to overlapping ranges
|
|
163
|
+
{
|
|
164
|
+
code: `type T = ((A | B) | C) | D;`,
|
|
165
|
+
output: [`type T = (A | B) | C | D;`, `type T = A | B | C | D;`],
|
|
166
|
+
errors: [
|
|
167
|
+
{
|
|
168
|
+
messageId: 'flattenParentheses',
|
|
169
|
+
line: 1,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
messageId: 'flattenParentheses',
|
|
173
|
+
line: 1,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Complex type names
|
|
179
|
+
{
|
|
180
|
+
code: `type Result = (Success<Data> | Error<Message>) | Loading;`,
|
|
181
|
+
output: `type Result = Success<Data> | Error<Message> | Loading;`,
|
|
182
|
+
errors: [
|
|
183
|
+
{
|
|
184
|
+
messageId: 'flattenParentheses',
|
|
185
|
+
line: 1,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
// Multiline union (Prettier cleans up extra whitespace)
|
|
191
|
+
{
|
|
192
|
+
code: `type T = (\n A | B\n) | C;`,
|
|
193
|
+
output: `type T = A | B\n | C;`,
|
|
194
|
+
errors: [
|
|
195
|
+
{
|
|
196
|
+
messageId: 'flattenParentheses',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
// Leading block comment inside parentheses - preserved in output
|
|
202
|
+
{
|
|
203
|
+
code: `type T = (/* comment */ A | B) | C;`,
|
|
204
|
+
output: `type T = /* comment */ A | B | C;`,
|
|
205
|
+
errors: [
|
|
206
|
+
{
|
|
207
|
+
messageId: 'flattenParentheses',
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Trailing block comment inside parentheses - preserved in output
|
|
213
|
+
{
|
|
214
|
+
code: `type T = (A | B /* comment */) | C;`,
|
|
215
|
+
output: `type T = A | B /* comment */ | C;`,
|
|
216
|
+
errors: [
|
|
217
|
+
{
|
|
218
|
+
messageId: 'flattenParentheses',
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// Trailing line comment - newline preserved so | C continues on next line
|
|
224
|
+
{
|
|
225
|
+
code: `type T = (A | B // comment\n) | C;`,
|
|
226
|
+
output: `type T = A | B // comment\n | C;`,
|
|
227
|
+
errors: [
|
|
228
|
+
{
|
|
229
|
+
messageId: 'flattenParentheses',
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
});
|