Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lab] Fix import paths in generated declaration files #24380

Merged
merged 4 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,8 @@ jobs:
git --no-pager diff

- run:
name: Log defect declaration files
command: |
# ignore build failures
# Fixing these takes some effort that isn't viable to merge in a single PR.
# We'll simply monitor them for now.
set +e
node scripts/testBuiltTypes.js
exit 0
name: Any defect declaration files?
command: node scripts/testBuiltTypes.js
- save_cache:
name: Save generated declaration files
key: typescript-declaration-files-{{ .Branch }}-{{ .Revision }}
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui-lab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"build:node": "node ../../scripts/build node",
"build:stable": "node ../../scripts/build stable",
"build:copy-files": "node ../../scripts/copy-files.js",
"build:types": "tsc -p tsconfig.build.json",
"build:types": "node ../../scripts/buildTypes",
"prebuild": "rimraf build",
"release": "yarn build && npm publish build --tag next",
"test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/material-ui-lab/**/*.test.{js,ts,tsx}'",
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"build:node": "node ../../scripts/build node",
"build:stable": "node ../../scripts/build stable",
"build:copy-files": "node ../../scripts/copy-files.js",
"build:types": "tsc -p tsconfig.build.json",
"build:types": "node ../../scripts/buildTypes",
"prebuild": "rimraf build",
"release": "yarn build && npm publish build --tag next",
"test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/material-ui-utils/**/*.test.{js,ts,tsx}'",
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"build:stable": "node ../../scripts/build stable",
"build:umd": "cross-env BABEL_ENV=stable rollup -c scripts/rollup.config.js",
"build:copy-files": "node ../../scripts/copy-files.js",
"build:types": "tsc -p tsconfig.build.json",
"build:types": "node ../../scripts/buildTypes",
"extract-error-codes": "cross-env MUI_EXTRACT_ERROR_CODES=true yarn build:modern",
"prebuild": "rimraf build tsconfig.build.tsbuildinfo",
"release": "yarn build && npm publish build --tag next",
Expand Down
139 changes: 139 additions & 0 deletions scripts/buildTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const chalk = require('chalk');
const childProcess = require('child_process');
const glob = require('fast-glob');
const fse = require('fs-extra');
const path = require('path');
const { promisify } = require('util');
const yargs = require('yargs');

const exec = promisify(childProcess.exec);

/**
* Fixes a wrong import path caused by https://github.com/microsoft/TypeScript/issues/39117
*
* @remarks Paths are hardcoded since it is unclear if all these broken import paths target "public paths".
*
* @param {string} importPath - POSIX path
*/
function rewriteImportPath(importPath) {
const coreSrcPath = path.posix.join('..', 'material-ui', 'src');
if (importPath.startsWith(coreSrcPath)) {
return importPath.replace(coreSrcPath, '@material-ui/core');
}

const stylesSrcPath = path.posix.join('..', 'material-ui-styles', 'src');
if (importPath.startsWith(stylesSrcPath)) {
return importPath.replace(stylesSrcPath, '@material-ui/styles');
}

throw new Error(`Don't know where to rewrite '${importPath}' to`);
}

async function main() {
const packageRoot = process.cwd();

const tsconfigPath = path.join(packageRoot, 'tsconfig.build.json');
if (!fse.existsSync(tsconfigPath)) {
throw new Error(
'Unable to find a tsconfig to build this project. ' +
`The package root needs to contain a 'tsconfig.build.json'. ` +
`The package root is '${packageRoot}'`,
);
}

await exec(['yarn', 'tsc', '-p', tsconfigPath].join(' '));

const publishDir = path.join(packageRoot, 'build');
const declarationFiles = await glob('**/*.d.ts', { absolute: true, cwd: publishDir });
if (declarationFiles.length === 0) {
throw new Error(`Unable to find declaration files in '${publishDir}'`);
}

async function rewriteImportPaths(declarationFile) {
const code = await fse.readFile(declarationFile, { encoding: 'utf8' });
let fixedCode = code;
const changes = [];

// find all type `import()`
// not to be confused with `import type`
const importTypeRegExp = /import\(([^)]+)\)/g;

let importTypeMatch;
// eslint-disable-next-line no-cond-assign -- Waiting for RegExp.prototype.matchAll
while ((importTypeMatch = importTypeRegExp.exec(code)) !== null) {
// First and last character are quotes.
// TypeScript mixes single and double quotes.
const importPath = importTypeMatch[1].slice(1, -1);
// In filesystem semantics `@material-ui/core` is a relative path.
// But when resolving imports these specifiers are considered "bare specifiers" and work differently.
// We're only interested in imports that are considered "relative path imports".
const isBareImportSpecifier = !importPath.startsWith('.');
if (!isBareImportSpecifier) {
const resolvedImport = path.resolve(declarationFile, importPath);
const importPathFromPublishDir = path.relative(publishDir, resolvedImport);
const isImportReachableWhenPublished = !importPathFromPublishDir.startsWith('.');

if (!isImportReachableWhenPublished) {
try {
const fixedImportPath = rewriteImportPath(
// ensure relative POSIX path
importPathFromPublishDir.replace(/\\/g, '/'),
);
const originalImportType = importTypeMatch[0];
const fixedImportType = importTypeMatch[0].replace(importPath, fixedImportPath);

// Make it easy to visually scan for the created lines.
changes.push(`-${chalk.bgRed(originalImportType)}\n+${chalk.bgGreen(fixedImportType)}`);
fixedCode = fixedCode.replace(originalImportType, fixedImportType);
} catch (error) {
throw new Error(`${declarationFile}: ${error}`);
}
}
}
}

const changed = changes.length > 0;
if (changed) {
await fse.writeFile(declarationFile, fixedCode);
}

return changes;
}

let rewrittenTally = 0;
let errorTally = 0;
await Promise.all(
declarationFiles.map(async (declarationFile) => {
try {
const rewrites = await rewriteImportPaths(declarationFile, publishDir);
if (rewrites.length > 0) {
// eslint-disable-next-line no-console -- Verbose logging
console.log(`${chalk.bgYellow`FIXED`} '${declarationFile}':\n${rewrites.join('\n')}`);
rewrittenTally += 1;
} else {
// eslint-disable-next-line no-console -- Verbose logging
console.log(`${chalk.bgGreen`OK`} '${declarationFile}'`);
}
} catch (error) {
console.error(error);
errorTally += 1;
process.exitCode = 1;
}
}),
);

// eslint-disable-next-line no-console -- Verbose logging
console.log(`Fixed: ${rewrittenTally}\nFailed: ${errorTally}\nTotal: ${declarationFiles.length}`);
}

yargs
.command({
command: '$0',
description:
'Builds a project with a fix for https://github.com/microsoft/TypeScript/issues/39117',
handler: main,
})
.help()
.strict(true)
.version(false)
.parse();