Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
183 lines
5.8 KiB
TypeScript
183 lines
5.8 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import * as ts from 'typescript';
|
|
import * as prettier from 'prettier';
|
|
import { kebabCase } from 'es-toolkit/string';
|
|
import { getLineFeed } from '@mui/internal-docs-utils';
|
|
import { replaceComponentLinks } from './utils/replaceUrl';
|
|
import { TypeScriptProject } from './utils/createTypeScriptProject';
|
|
|
|
export type { ComponentInfo, HookInfo } from './types/utils.types';
|
|
|
|
/**
|
|
* TODO: this should really be fixed in findPagesMarkdown().
|
|
* Plus replaceComponentLinks() shouldn't exist in the first place,
|
|
* the markdown folder location should match the URLs.
|
|
*/
|
|
export function fixPathname(pathname: string): string {
|
|
let fixedPathname;
|
|
|
|
if (pathname.startsWith('/material')) {
|
|
fixedPathname = replaceComponentLinks(`${pathname.replace(/^\/material/, '')}/`);
|
|
} else if (pathname.startsWith('/joy')) {
|
|
fixedPathname = replaceComponentLinks(`${pathname.replace(/^\/joy/, '')}/`).replace(
|
|
'material-ui',
|
|
'joy-ui',
|
|
);
|
|
} else if (pathname.startsWith('/base')) {
|
|
fixedPathname = `${pathname
|
|
.replace('/base/', '/base-ui/')
|
|
.replace('/components/', '/react-')}/`;
|
|
} else {
|
|
fixedPathname = `${pathname.replace('/components/', '/react-')}/`;
|
|
}
|
|
|
|
return fixedPathname;
|
|
}
|
|
|
|
const DEFAULT_PRETTIER_CONFIG_PATH = path.join(process.cwd(), 'prettier.config.mjs');
|
|
|
|
export async function writePrettifiedFile(
|
|
filename: string,
|
|
data: string,
|
|
prettierConfigPath: string = DEFAULT_PRETTIER_CONFIG_PATH,
|
|
options: object = {},
|
|
) {
|
|
const prettierConfig = await prettier.resolveConfig(filename, {
|
|
config: prettierConfigPath,
|
|
});
|
|
if (prettierConfig === null) {
|
|
throw new Error(
|
|
`Could not resolve config for '${filename}' using prettier config path '${prettierConfigPath}'.`,
|
|
);
|
|
}
|
|
|
|
const formatted = await prettier.format(data, { ...prettierConfig, filepath: filename });
|
|
fs.writeFileSync(filename, formatted, {
|
|
encoding: 'utf8',
|
|
...options,
|
|
});
|
|
}
|
|
|
|
let systemComponents: string[] | undefined;
|
|
// making the resolution lazy to avoid issues when importing something irrelevant from this file (i.e. `getSymbolDescription`)
|
|
// the eager resolution results in errors when consuming externally (i.e. `mui-x`)
|
|
export function getSystemComponents() {
|
|
if (!systemComponents) {
|
|
systemComponents = fs
|
|
.readdirSync(path.resolve('packages', 'mui-system', 'src'))
|
|
// Normalization, the Unstable_ prefix doesn't count.
|
|
.map((pathname) => pathname.replace('Unstable_', ''))
|
|
.filter((pathname) => pathname.match(/^[A-Z][a-zA-Z]+$/));
|
|
}
|
|
return systemComponents;
|
|
}
|
|
|
|
export function getMuiName(name: string) {
|
|
return `Mui${name.replace('Styled', '')}`;
|
|
}
|
|
|
|
export function extractPackageFile(filePath: string) {
|
|
filePath = filePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
|
const match = filePath.match(
|
|
/.*\/packages.*\/(?<packagePath>[^/]+)\/src\/(.*\/)?(?<name>[^/]+)\.(js|tsx|ts|d\.ts)/,
|
|
);
|
|
const result = {
|
|
packagePath: match ? match.groups?.packagePath : null,
|
|
name: match ? match.groups?.name : null,
|
|
};
|
|
return {
|
|
...result,
|
|
muiPackage: result.packagePath?.replace('x-', 'mui-'),
|
|
};
|
|
}
|
|
|
|
export function parseFile(filename: string) {
|
|
const src = fs.readFileSync(filename, 'utf8');
|
|
return {
|
|
src,
|
|
shouldSkip:
|
|
filename.includes('internal') ||
|
|
!!src.match(/@ignore - internal component\./) ||
|
|
!!src.match(/@ignore - internal hook\./) ||
|
|
!!src.match(/@ignore - do not document\./),
|
|
spread: !src.match(/ = exactProp\(/),
|
|
EOL: getLineFeed(src),
|
|
inheritedComponent: src.match(/\/\/ @inheritedComponent (.*)/)?.[1],
|
|
};
|
|
}
|
|
|
|
export function getApiPath(
|
|
demos: Array<{ demoPageTitle: string; demoPathname: string }>,
|
|
name: string,
|
|
) {
|
|
let apiPath = null;
|
|
|
|
if (demos && demos.length > 0) {
|
|
// remove the hash from the demoPathname, for e.g. "#hooks"
|
|
const cleanedDemosPathname = demos[0].demoPathname.split('#')[0];
|
|
apiPath = `${cleanedDemosPathname}${
|
|
name.startsWith('use') ? 'hooks-api' : 'components-api'
|
|
}/#${kebabCase(name)}`;
|
|
}
|
|
|
|
return apiPath;
|
|
}
|
|
|
|
export async function formatType(rawType: string) {
|
|
if (!rawType) {
|
|
return '';
|
|
}
|
|
|
|
const prefix = 'type FakeType = ';
|
|
const signatureWithTypeName = `${prefix}${rawType}`;
|
|
|
|
const prettifiedSignatureWithTypeName = await prettier.format(signatureWithTypeName, {
|
|
printWidth: 999,
|
|
singleQuote: true,
|
|
semi: false,
|
|
trailingComma: 'none',
|
|
parser: 'typescript',
|
|
});
|
|
|
|
return prettifiedSignatureWithTypeName.slice(prefix.length).replace(/\n$/, '');
|
|
}
|
|
|
|
export function getSymbolDescription(symbol: ts.Symbol, project: TypeScriptProject) {
|
|
return symbol
|
|
.getDocumentationComment(project.checker)
|
|
.flatMap((comment) => comment.text.split('\n'))
|
|
.filter((line) => !line.startsWith('TODO'))
|
|
.join('\n');
|
|
}
|
|
|
|
export function getSymbolJSDocTags(symbol: ts.Symbol) {
|
|
return Object.fromEntries(symbol.getJsDocTags().map((tag) => [tag.name, tag]));
|
|
}
|
|
|
|
export async function stringifySymbol(symbol: ts.Symbol, project: TypeScriptProject) {
|
|
let rawType: string;
|
|
|
|
const declaration = symbol.declarations?.[0];
|
|
if (declaration && ts.isPropertySignature(declaration)) {
|
|
rawType = declaration.type?.getText() ?? '';
|
|
} else {
|
|
rawType = project.checker.typeToString(
|
|
project.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!),
|
|
symbol.valueDeclaration,
|
|
ts.TypeFormatFlags.NoTruncation,
|
|
);
|
|
}
|
|
|
|
return formatType(rawType);
|
|
}
|
|
|
|
/**
|
|
* @param filepath - absolute path
|
|
* @example toGitHubPath('/home/user/material-ui/packages/Accordion') === '/packages/Accordion'
|
|
* @example toGitHubPath('C:\\Development\\material-ui\\packages\\Accordion') === '/packages/Accordion'
|
|
*/
|
|
export function toGitHubPath(filepath: string): string {
|
|
return `/${path.relative(process.cwd(), filepath).replace(/\\/g, '/')}`;
|
|
}
|