mirror of
https://github.com/google/blockly.git
synced 2026-01-11 02:47:09 +01:00
chore: Create script to help with js -> ts migration (#6837)
This commit is contained in:
committed by
GitHub
parent
13fe6eeccf
commit
ddd38a411a
154
scripts/migration/js2ts
Executable file
154
scripts/migration/js2ts
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const filenames = process.argv.slice(2); // Trim off node and script name.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Load deps files via require (since they're executalbe .js files).
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Dictionary mapping goog.module ID to absolute pathname of the file
|
||||
* containing the goog.declareModuleId for that ID.
|
||||
* @type {!Object<string, string>}
|
||||
*/
|
||||
const modulePaths = {};
|
||||
|
||||
/** Absolute path of repository root. */
|
||||
const repoPath = path.resolve(__dirname, '..', '..');
|
||||
|
||||
/**
|
||||
* Absolute path of directory containing base.js (the version used as
|
||||
* input to tsc, not the one output by it).
|
||||
* @type {string}
|
||||
*/
|
||||
const closurePath = path.resolve(repoPath, 'closure', 'goog');
|
||||
|
||||
globalThis.goog = {};
|
||||
|
||||
/**
|
||||
* Stub version of addDependency that store mappings in modulePaths.
|
||||
* @param {string} relPath The path to the js file.
|
||||
* @param {!Array<string>} provides An array of strings with
|
||||
* the names of the objects this file provides.
|
||||
* @param {!Array<string>} _requires An array of strings with
|
||||
* the names of the objects this file requires (unused).
|
||||
* @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating
|
||||
* how the file must be loaded. The boolean 'true' is equivalent
|
||||
* to {'module': 'goog'} for backwards-compatibility. Valid properties
|
||||
* and values include {'module': 'goog'} and {'lang': 'es6'}.
|
||||
*/
|
||||
goog.addDependency = function(relPath, provides, _requires, opt_loadFlags) {
|
||||
// Ignore any non-ESM files, as they can't be imported.
|
||||
if (opt_loadFlags?.module !== 'es6') return;
|
||||
|
||||
// There should be only one "provide" from an ESM, but...
|
||||
for (const moduleId of provides) {
|
||||
// Store absolute path to source file (i.e., treating relPath
|
||||
// relative to closure/goog/, not build/src/closure/goog/).
|
||||
modulePaths[moduleId] = path.resolve(closurePath, relPath);
|
||||
}
|
||||
};
|
||||
|
||||
// Load deps files relative to this script's location.
|
||||
require(path.resolve(__dirname, '../../build/deps.js'));
|
||||
require(path.resolve(__dirname, '../../build/deps.mocha.js'));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Process files mentioned on the command line.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** RegExp matching goog.require statements. */
|
||||
const requireRE =
|
||||
/(?:const\s+(?:([$\w]+)|(\{[^}]*\}))\s+=\s+)?goog.require(Type)?\('([^']+)'\);/mg;
|
||||
|
||||
for (const filename of filenames) {
|
||||
let contents = null;
|
||||
try {
|
||||
contents = String(fs.readFileSync(filename));
|
||||
} catch (e) {
|
||||
console.error(`error while reading ${filename}: ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
console.log(`Converting ${filename} to TypeScript...`);
|
||||
|
||||
// Remove "use strict".
|
||||
contents = contents.replace(/^\s*["']use strict["']\s*; *\n/m, '');
|
||||
|
||||
// Migrate from goog.module to goog.declareModuleId.
|
||||
const closurePathRelative =
|
||||
path.relative(path.dirname(path.resolve(filename)), closurePath);
|
||||
contents = contents.replace(
|
||||
/^goog.module\('([$\w.]+)'\);$/m,
|
||||
`import * as goog from '${closurePathRelative}/goog.js';\n` +
|
||||
`goog.declareModuleId('$1');`);
|
||||
|
||||
// Migrate from goog.require to import.
|
||||
contents = contents.replace(
|
||||
requireRE,
|
||||
function(
|
||||
orig, // Whole statement to be replaced.
|
||||
name, // Name of named import of whole module (if applicable).
|
||||
names, // {}-enclosed list of destructured imports.
|
||||
type, // If truthy, it is a requireType not require.
|
||||
moduleId, // goog.module ID that was goog.require()d.
|
||||
) {
|
||||
const importPath = modulePaths[moduleId];
|
||||
type = type ? ' type' : '';
|
||||
if (!importPath) {
|
||||
console.warn(`Unable to migrate goog.require('${
|
||||
moduleId}') as no ES module path known.`);
|
||||
return orig;
|
||||
}
|
||||
const relativePath =
|
||||
path.relative(path.dirname(path.resolve(filename)), importPath);
|
||||
if (name) {
|
||||
return `import${type} * as ${name} from '${relativePath}';`;
|
||||
} else if (names) {
|
||||
return `import${type} ${names} from '${relativePath}';`;
|
||||
} else { // Side-effect only require.
|
||||
return `import${type} '${relativePath}';`;
|
||||
}
|
||||
});
|
||||
|
||||
// Find and update or remove old-style export assignemnts.
|
||||
/** @type {!Array<{name: string, re: RegExp>}>} */
|
||||
const easyExports = [];
|
||||
contents = contents.replace(
|
||||
/^\s*exports\.([$\w]+)\s*=\s*([$\w]+)\s*;\n/gm,
|
||||
function(
|
||||
orig, // Whole statement to be replaced.
|
||||
exportName, // Name to export item as.
|
||||
declName, // Already-declared name for item being exported.
|
||||
) {
|
||||
// Renamed exports have to be transalted as-is.
|
||||
if (exportName !== declName) {
|
||||
return `export {${declName} as ${exportName}};\n`;
|
||||
}
|
||||
// OK, we're doing "export.foo = foo;". Can we update the
|
||||
// declaration? We can't actualy modify it yet as we're in
|
||||
// the middle of a search-and-replace on contents already, but
|
||||
// we can delete the old export and later update the
|
||||
// declaration into an export.
|
||||
const declRE = new RegExp(
|
||||
`^(\\s*)((?:const|let|var|function|class)\\s+${declName})\\b`,
|
||||
'gm');
|
||||
if (contents.match(declRE)) {
|
||||
easyExports.push({exportName, declRE});
|
||||
return ''; // Delete existing export assignment.
|
||||
} else {
|
||||
return `export ${exportName};\n`; // Safe fallback.
|
||||
}
|
||||
});
|
||||
// Add 'export' to existing declarations where appropriate.
|
||||
for (const {exportName, declRE} of easyExports) {
|
||||
contents = contents.replace(declRE, '$1export $2');
|
||||
}
|
||||
|
||||
// Write converted file with new extension.
|
||||
const newFilename = filename.replace(/.js$/, '.ts');
|
||||
fs.writeFileSync(newFilename, contents);
|
||||
console.log(`Wrote ${newFilename}.`);
|
||||
}
|
||||
Reference in New Issue
Block a user