@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.
@@ -0,0 +1,5 @@
1
+ declare const _default: ESLintUtils.RuleModule<"flattenParentheses", [], unknown, ESLintUtils.RuleListener> & {
2
+ name: string;
3
+ };
4
+ export default _default;
5
+ import { ESLintUtils } from '@typescript-eslint/utils';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-code-infra",
3
- "version": "0.0.3-canary.87",
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": "4ade0c29b3effbc7af0278f249019ce294fed0d4",
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',
@@ -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
+ });