mirror of
https://github.com/facebook/lexical.git
synced 2025-05-17 06:59:17 +08:00
175 lines
4.9 KiB
JavaScript
175 lines
4.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
*/
|
|
// @ts-check
|
|
'use strict';
|
|
|
|
const fs = require('fs-extra');
|
|
const ts = require('typescript');
|
|
const tsMorph = require('ts-morph');
|
|
const hermesParser = require('hermes-parser');
|
|
const {packagesManager} = require('./shared/packagesManager');
|
|
|
|
const pretty = process.env.CI !== 'true';
|
|
|
|
/** @type {ts.FormatDiagnosticsHost} */
|
|
const diagnosticsHost = {
|
|
getCanonicalFileName: (fn) => fn,
|
|
getCurrentDirectory: () => './',
|
|
getNewLine: () => '\n',
|
|
};
|
|
|
|
/**
|
|
* Validate that the manually maintained .flow types have the same exports as
|
|
* the corresponding .d.ts types produced by the build.
|
|
*
|
|
* `process.exit(1)` on failure.
|
|
*/
|
|
function lintFlowTypes() {
|
|
let didError = false;
|
|
const project = new tsMorph.Project({tsConfigFilePath: './tsconfig.json'});
|
|
for (const pkg of packagesManager.getPublicPackages()) {
|
|
didError = lintFlowTypesForPackage(project, pkg) || didError;
|
|
}
|
|
if (didError) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function collectFlowExports(flowAst) {
|
|
const exportNames = new Map();
|
|
const exportId = (node) => {
|
|
const identifier =
|
|
node.type === 'Identifier'
|
|
? node
|
|
: 'id' in node && node.id.type === 'Identifier'
|
|
? node.id
|
|
: null;
|
|
if (identifier) {
|
|
exportNames.set(identifier.name, identifier);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
hermesParser.SimpleTraverser.traverse(flowAst, {
|
|
enter: (node, parent) => {
|
|
if (
|
|
parent &&
|
|
(parent.type === 'DeclareExportDeclaration' ||
|
|
parent.type === 'ExportNamedDeclaration')
|
|
) {
|
|
if (exportId(node)) {
|
|
// ok
|
|
} else if (node.type === 'VariableDeclaration') {
|
|
for (const declaration of node.declarations) {
|
|
if (!exportId(declaration)) {
|
|
// debugger;
|
|
}
|
|
}
|
|
} else if (node.type === 'ExportSpecifier') {
|
|
if (!exportId(node.exported)) {
|
|
// debugger;
|
|
}
|
|
} else {
|
|
// debugger;
|
|
}
|
|
}
|
|
},
|
|
leave: () => {},
|
|
});
|
|
return exportNames;
|
|
}
|
|
|
|
function compareFlowDts(
|
|
/** @type {PackageMetadata} */ pkg,
|
|
/** @type {string} */ flowFilePath,
|
|
/** @type {tsMorph.SourceFile} */ entrypoint,
|
|
/** @type {ts.Diagnostic[]} */ diagnostics,
|
|
/** @type {import('hermes-estree').Identifier[]} */ flowDiagnostics,
|
|
) {
|
|
const flowAst = hermesParser.parse(fs.readFileSync(flowFilePath, 'utf-8'), {
|
|
enableExperimentalComponentSyntax: true,
|
|
flow: 'all',
|
|
sourceFilename: flowFilePath,
|
|
sourceType: 'module',
|
|
});
|
|
const flowMap = collectFlowExports(flowAst);
|
|
const symbols = entrypoint.getExportSymbols();
|
|
const tsMap = new Map(symbols.map((sym) => [sym.getName(), sym]));
|
|
for (const [name, symbol] of tsMap) {
|
|
if (flowMap.has(name)) {
|
|
continue;
|
|
}
|
|
for (const decl of symbol.getDeclarations()) {
|
|
const start = decl.getStart();
|
|
const end = decl.getEnd();
|
|
diagnostics.push({
|
|
category: ts.DiagnosticCategory.Warning,
|
|
code: Infinity,
|
|
file: entrypoint.compilerNode,
|
|
length: end - start,
|
|
messageText: `Missing flow export for TypeScript export '${name}'`,
|
|
start,
|
|
});
|
|
break;
|
|
}
|
|
// debugger;
|
|
}
|
|
for (const [name, flowToken] of flowMap) {
|
|
if (tsMap.has(name)) {
|
|
continue;
|
|
}
|
|
flowDiagnostics.push(flowToken);
|
|
}
|
|
}
|
|
|
|
function lintFlowTypesForPackage(
|
|
/** @type {tsMorph.Project} */ project,
|
|
/** @type {PackageMetadata} */ pkg,
|
|
) {
|
|
const def = pkg.getPackageBuildDefinition();
|
|
if (def.packageName === 'lexical-eslint-plugin') {
|
|
return false;
|
|
}
|
|
/** @type {ts.Diagnostic[]} */
|
|
const diagnostics = [];
|
|
/** @type {import('hermes-estree').Identifier[]} */
|
|
const flowDiagnostics = [];
|
|
for (const {outputFileName, sourceFileName} of def.modules) {
|
|
const entrypoint = project.addSourceFileAtPath(
|
|
pkg.resolve('src', sourceFileName),
|
|
);
|
|
const flowFilePath = pkg.resolve('flow', `${outputFileName}.js.flow`);
|
|
if (!fs.existsSync(flowFilePath)) {
|
|
console.error(`Missing ${flowFilePath}`);
|
|
process.exit(1);
|
|
}
|
|
compareFlowDts(pkg, flowFilePath, entrypoint, diagnostics, flowDiagnostics);
|
|
}
|
|
if (diagnostics.length > 0 || flowDiagnostics.length > 0) {
|
|
const msg = (
|
|
pretty ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics
|
|
)(diagnostics, diagnosticsHost);
|
|
if (msg) {
|
|
console.error(msg.replace(/ TSInfinity:/g, ':'));
|
|
}
|
|
const flowMsg = flowDiagnostics
|
|
.map(
|
|
(ident) =>
|
|
`${ident.loc.source}:${ident.loc.start.line}:${ident.loc.start.column} - warning: Flow export '${ident.name}' does not have a TypeScript declaration`,
|
|
)
|
|
.join('\n');
|
|
if (flowMsg) {
|
|
console.error(flowMsg);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
lintFlowTypes();
|