diff --git a/.circleci/config.yml b/.circleci/config.yml index e2d955df8d7671..64827aa7989ec8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -258,12 +258,6 @@ jobs: - run: name: '`pnpm proptypes` changes committed?' command: git add -A && git diff --exit-code --staged - - run: - name: 'Write "use client" directive' - command: pnpm rsc:build - - run: - name: '`pnpm rsc:build` changes committed?' - command: git add -A && git diff --exit-code --staged - run: name: Generate the documentation command: pnpm docs:api diff --git a/package.json b/package.json index 2404cb680c7eb4..63fe5f689a1352 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "docs:mdicons:synonyms": "cross-env BABEL_ENV=development babel-node --extensions \".tsx,.ts,.js,.mjs\" ./docs/scripts/updateIconSynonyms && pnpm prettier", "docs:zipRules": "cd docs && rm mui-vale.zip && zip -r mui-vale.zip mui-vale && cd ../ && vale sync", "extract-error-codes": "cross-env MUI_EXTRACT_ERROR_CODES=true lerna run --concurrency 1 build:modern", - "rsc:build": "tsx ./packages/rsc-builder/buildRsc.ts", "template:screenshot": "cross-env BABEL_ENV=development babel-node --extensions \".tsx,.ts,.js\" ./docs/scripts/generateTemplateScreenshots", "install:codesandbox": "pnpm install --no-frozen-lockfile", "jsonlint": "node ./scripts/jsonlint.mjs", diff --git a/packages/eslint-plugin-material-ui/src/rules/disallow-react-api-in-server-components.js b/packages/eslint-plugin-material-ui/src/rules/disallow-react-api-in-server-components.js index 02d894e764c133..d514a601958028 100644 --- a/packages/eslint-plugin-material-ui/src/rules/disallow-react-api-in-server-components.js +++ b/packages/eslint-plugin-material-ui/src/rules/disallow-react-api-in-server-components.js @@ -1,43 +1,56 @@ -module.exports = { +/// @ts-check + +const REACT_CLIENT_APIS = new Set([ + 'useState', + 'useEffect', + 'useLayoutEffect', + 'useReducer', + 'useTransition', + 'createContext', +]); + +/** + * @param {import('eslint').AST.Program} ast + * @param {string} directive + * @returns + */ +function hasDirective(ast, directive) { + return ast.body.some( + (statement) => + statement.type === 'ExpressionStatement' && + statement.expression.type === 'Literal' && + statement.expression.value === directive, + ); +} + +module.exports = /** @type {import('eslint').Rule.RuleModule} */ ({ create(context) { let hasUseClientDirective = false; - const apis = new Set([ - 'useState', - 'useEffect', - 'useLayoutEffect', - 'useReducer', - 'useTransition', - 'createContext', - ]); + let hasUseServerDirective = false; return { + /** @param {import('eslint').AST.Program} node */ Program(node) { - hasUseClientDirective = node.body.some( - (statement) => - statement.type === 'ExpressionStatement' && - statement.expression.type === 'Literal' && - statement.expression.value === 'use client', - ); + hasUseServerDirective = hasDirective(node, 'use server'); + hasUseClientDirective = hasDirective(node, 'use client'); }, CallExpression(node) { if ( !hasUseClientDirective && node.callee.type === 'MemberExpression' && + node.callee.object.type === 'Identifier' && node.callee.object.name === 'React' && - apis.has(node.callee.property.name) + node.callee.property.type === 'Identifier' && + REACT_CLIENT_APIS.has(node.callee.property.name) ) { context.report({ node, message: `Using 'React.${node.callee.property.name}' is forbidden if the file doesn't have a 'use client' directive.`, fix(fixer) { - const sourceCode = context.getSourceCode(); - if ( - sourceCode.text.includes('"use server"') || - sourceCode.text.includes("'use server'") - ) { + if (hasUseServerDirective) { return null; } - const firstToken = sourceCode.ast.body[0]; + const firstToken = context.sourceCode.ast.body[0]; return fixer.insertTextBefore(firstToken, "'use client';\n"); }, }); @@ -48,4 +61,4 @@ module.exports = { meta: { fixable: 'code', }, -}; +}); diff --git a/packages/rsc-builder/buildRsc.ts b/packages/rsc-builder/buildRsc.ts deleted file mode 100644 index 72211bac33a6d2..00000000000000 --- a/packages/rsc-builder/buildRsc.ts +++ /dev/null @@ -1,176 +0,0 @@ -import path from 'path'; -import * as yargs from 'yargs'; -import * as fse from 'fs-extra'; -import findComponents from '@mui-internal/api-docs-builder/utils/findComponents'; -import findHooks from '@mui-internal/api-docs-builder/utils/findHooks'; - -type CommandOptions = { grep?: string }; - -type Project = { - name: string; - rootPath: string; - additionalPaths?: string[]; - additionalFiles?: string[]; - ignorePaths?: string[]; -}; - -const PROJECTS: Project[] = [ - { - name: 'base', - rootPath: path.join(process.cwd(), 'packages/mui-base'), - }, - { - name: 'material', - rootPath: path.join(process.cwd(), 'packages/mui-material'), - ignorePaths: [ - 'packages/mui-material/src/InitColorSchemeScript/InitColorSchemeScript.tsx', // RSC compatible - 'packages/mui-material/src/PigmentContainer/PigmentContainer.tsx', // RSC compatible - 'packages/mui-material/src/PigmentGrid/PigmentGrid.tsx', // RSC compatible - 'packages/mui-material/src/PigmentStack/PigmentStack.tsx', // RSC compatible - ], - }, - { - name: 'joy', - rootPath: path.join(process.cwd(), 'packages/mui-joy'), - ignorePaths: [ - 'packages/mui-joy/src/InitColorSchemeScript/InitColorSchemeScript.tsx', // no need 'use client' because of `styles/index` export - ], - }, - { - name: 'system', - rootPath: path.join(process.cwd(), 'packages/mui-system'), - ignorePaths: [ - 'packages/mui-system/src/InitColorSchemeScript/InitColorSchemeScript.tsx', // no need 'use client' because of `styles/index` export - ], - }, - { - name: 'styled-engine', - rootPath: path.join(process.cwd(), 'packages/mui-styled-engine'), - }, - { - name: 'utils', - rootPath: path.join(process.cwd(), 'packages/mui-utils'), - }, - { - name: 'icons-material', - rootPath: path.join(process.cwd(), 'packages/mui-icons-material'), - additionalPaths: ['custom'], - additionalFiles: ['src/utils/createSvgIcon.js'], - }, - { - name: 'lab', - rootPath: path.join(process.cwd(), 'packages/mui-lab'), - }, -]; - -async function processFile( - filename: string, - options: { - lineToPrepend?: string; - } = {}, -) { - if (!fse.statSync(filename).isFile()) { - return; - } - - const { lineToPrepend = `'use client';` } = options; - const contents = await fse.readFile(filename, 'utf8'); - - const lines = contents.split(/\r?\n/); - if (lines[0] === lineToPrepend) { - return; - } - - const newContents = `${lineToPrepend}\n${contents}`; - - await fse.writeFile(filename, newContents); -} - -async function findAll( - directories: string[], - grep: RegExp | null, - findFn: typeof findComponents | typeof findHooks, -) { - const result = await Promise.all( - directories.map((dir) => { - return findFn(dir).filter((item) => { - if (grep === null) { - return true; - } - return grep.test(item.filename); - }); - }), - ); - - return result.flat(); -} - -async function run(argv: yargs.ArgumentsCamelCase) { - const grep = argv.grep == null ? null : new RegExp(argv.grep); - - await PROJECTS.reduce(async (resolvedPromise, project) => { - await resolvedPromise; - - const projectSrc = path.join(project.rootPath, 'src'); - - let directories = [projectSrc]; - - if (Array.isArray(project?.additionalPaths)) { - directories = [ - ...directories, - ...project.additionalPaths.map((p) => path.join(project.rootPath, p)), - ]; - } - - const components = await findAll(directories, grep, findComponents); - - components.forEach(async (component) => { - try { - if (!project.ignorePaths?.some((p) => component.filename.includes(p))) { - processFile(component.filename); - } - } catch (error: any) { - error.message = `${path.relative(process.cwd(), component.filename)}: ${error.message}`; - throw error; - } - }); - - const hooks = await findAll(directories, grep, findHooks); - - hooks.forEach(async (hook) => { - try { - processFile(hook.filename); - } catch (error: any) { - error.message = `${path.relative(process.cwd(), hook.filename)}: ${error.message}`; - throw error; - } - }); - - if (Array.isArray(project?.additionalFiles)) { - project.additionalFiles.forEach(async (file) => { - const fullPath = path.join(project.rootPath, file); - processFile(fullPath); - }); - } - - return Promise.resolve(); - }, Promise.resolve()); -} - -yargs - .command({ - command: '$0', - describe: 'prepends the use client directive to components', - builder: (command) => { - return command.option('grep', { - description: - 'Only process files for component filenames matching the pattern. The string is treated as a RegExp.', - type: 'string', - }); - }, - handler: run, - }) - .help() - .strict(true) - .version(false) - .parse(); diff --git a/packages/rsc-builder/package.json b/packages/rsc-builder/package.json deleted file mode 100644 index 728976baa4f5f5..00000000000000 --- a/packages/rsc-builder/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "rsc-builder", - "version": "1.0.0", - "private": "true", - "main": "./buildRsc.ts", - "dependencies": { - "fs-extra": "^11.2.0", - "yargs": "^17.7.2" - }, - "devDependencies": { - "@types/mocha": "^10.0.10", - "@types/node": "^20.17.12" - } -} diff --git a/packages/rsc-builder/tsconfig.json b/packages/rsc-builder/tsconfig.json deleted file mode 100644 index 4a58f12da89c7e..00000000000000 --- a/packages/rsc-builder/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "isolatedModules": true, - "noEmit": true, - "noUnusedLocals": false, - "resolveJsonModule": true, - "skipLibCheck": true, - "esModuleInterop": true, - "types": ["node", "mocha"], - "target": "ES2020", - "module": "CommonJS", - "moduleResolution": "node", - "strict": true, - "baseUrl": "./" - }, - "include": ["./**/*.ts", "./**/*.js"], - "exclude": ["node_modules"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2026094314f7c5..0d0b55012c5df8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2209,22 +2209,6 @@ importers: specifier: 0.14.2 version: 0.14.2 - packages/rsc-builder: - dependencies: - fs-extra: - specifier: ^11.2.0 - version: 11.2.0 - yargs: - specifier: ^17.7.2 - version: 17.7.2 - devDependencies: - '@types/mocha': - specifier: ^10.0.10 - version: 10.0.10 - '@types/node': - specifier: ^20.17.12 - version: 20.17.12 - packages/waterfall: {} scripts/sizeSnapshot: