From 28c4de9e4340e92254d3957adef9e1f61877537a Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Thu, 18 Apr 2024 09:19:43 -0700 Subject: [PATCH 1/2] fix(@lexical/utils): fix #5918 by re-exporting shared/* constants with explicit types --- packages/lexical-utils/src/index.ts | 38 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/lexical-utils/src/index.ts b/packages/lexical-utils/src/index.ts index 8d981b5e634..1d4bd037de0 100644 --- a/packages/lexical-utils/src/index.ts +++ b/packages/lexical-utils/src/index.ts @@ -24,7 +24,20 @@ import { LexicalEditor, LexicalNode, } from 'lexical'; -import {IS_FIREFOX} from 'shared/environment'; +// This underscore postfixing is used as a hotfix so we do not +// export shared types from this module #5918 +import {CAN_USE_DOM as CAN_USE_DOM_} from 'shared/canUseDOM'; +import { + CAN_USE_BEFORE_INPUT as CAN_USE_BEFORE_INPUT_, + IS_ANDROID as IS_ANDROID_, + IS_ANDROID_CHROME as IS_ANDROID_CHROME_, + IS_APPLE as IS_APPLE_, + IS_APPLE_WEBKIT as IS_APPLE_WEBKIT_, + IS_CHROME as IS_CHROME_, + IS_FIREFOX as IS_FIREFOX_, + IS_IOS as IS_IOS_, + IS_SAFARI as IS_SAFARI_, +} from 'shared/environment'; import invariant from 'shared/invariant'; import normalizeClassNames from 'shared/normalizeClassNames'; @@ -32,18 +45,17 @@ export {default as markSelection} from './markSelection'; export {default as mergeRegister} from './mergeRegister'; export {default as positionNodeOnRange} from './positionNodeOnRange'; export {$splitNode, isHTMLAnchorElement, isHTMLElement} from 'lexical'; -export {CAN_USE_DOM} from 'shared/canUseDOM'; -export { - CAN_USE_BEFORE_INPUT, - IS_ANDROID, - IS_ANDROID_CHROME, - IS_APPLE, - IS_APPLE_WEBKIT, - IS_CHROME, - IS_FIREFOX, - IS_IOS, - IS_SAFARI, -} from 'shared/environment'; +// Hotfix to export these with inlined types #5918 +export const CAN_USE_BEFORE_INPUT: boolean = CAN_USE_BEFORE_INPUT_; +export const CAN_USE_DOM: boolean = CAN_USE_DOM_; +export const IS_ANDROID: boolean = IS_ANDROID_; +export const IS_ANDROID_CHROME: boolean = IS_ANDROID_CHROME_; +export const IS_APPLE: boolean = IS_APPLE_; +export const IS_APPLE_WEBKIT: boolean = IS_APPLE_WEBKIT_; +export const IS_CHROME: boolean = IS_CHROME_; +export const IS_FIREFOX: boolean = IS_FIREFOX_; +export const IS_IOS: boolean = IS_IOS_; +export const IS_SAFARI: boolean = IS_SAFARI_; export type DFSNode = Readonly<{ depth: number; From bae93d3a90101165c79a3e40de5448a89f42f81b Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Thu, 18 Apr 2024 12:45:13 -0700 Subject: [PATCH 2/2] Add validate-tsc-types type validation step after build --- package.json | 5 +- scripts/validate-tsc-types.js | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 scripts/validate-tsc-types.js diff --git a/package.json b/package.json index 1ec7a0e3482..851f1151620 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,11 @@ "dev": "npm run dev --prefix packages/lexical-playground", "start-test-server": "npm run preview --prefix packages/lexical-playground -- --port 4000", "build": "node scripts/build.js", - "build-prod": "npm run clean && npm run build -- --prod", + "build-prod": "npm run clean && npm run build -- --prod && node ./scripts/validate-tsc-types.js", "build-playground-prod": "npm run build-prod && npm run build-prod --prefix packages/lexical-playground", - "build-release": "npm run build-prod -- --release", + "build-release": "npm run build-prod -- --release && node ./scripts/validate-tsc-types.js", "build-www": "npm run clean && npm run build -- --www && npm run build -- --www --prod && npm run prepare-www", + "build-types": "tsc -p ./tsconfig.build.json && node ./scripts/validate-tsc-types.js", "clean": "node scripts/clean.js", "extract-codes": "node scripts/build.js --codes", "flow": "node ./scripts/check-flow-types.js", diff --git a/scripts/validate-tsc-types.js b/scripts/validate-tsc-types.js new file mode 100644 index 00000000000..3c9a1220f89 --- /dev/null +++ b/scripts/validate-tsc-types.js @@ -0,0 +1,88 @@ +/** + * 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 glob = require('glob'); +const ts = require('typescript'); + +const pretty = process.env.CI !== 'true'; + +/** @type {ts.FormatDiagnosticsHost} */ +const diagnosticsHost = { + getCanonicalFileName: (fn) => fn, + getCurrentDirectory: () => './', + getNewLine: () => '\n', +}; + +/** + * Validate that the published .d.ts types do not have dependencies + * on any private module (currently shared/*). + * + * `process.exit(1)` on failure. + */ +function validateTscTypes() { + const dtsFilesPattern = './.ts-temp/{lexical,lexical-*}/**/*.d.ts'; + const dtsFiles = glob.sync(dtsFilesPattern); + if (dtsFiles.length === 0) { + console.error( + `Missing ${dtsFilesPattern}, \`npm run build-prod\` or \`npm run build-release\` first`, + ); + process.exit(1); + } + /** @type {ts.Diagnostic[]} */ + const diagnostics = []; + for (const fn of dtsFiles) { + // console.log(fn); + const ast = ts.createSourceFile( + fn, + fs.readFileSync(fn, 'utf-8'), + ts.ScriptTarget.Latest, + ); + const checkSpecifier = (/** @type {ts.Node | undefined} */ node) => { + if (!node || node.kind !== ts.SyntaxKind.StringLiteral) { + return; + } + const specifier = /** @type {import('typescript').StringLiteral} */ ( + node + ); + if (/^shared(\/|$)/.test(specifier.text)) { + const start = specifier.getStart(ast); + diagnostics.push({ + category: ts.DiagnosticCategory.Error, + code: Infinity, + file: ast, + length: specifier.getEnd() - start, + messageText: `Published .d.ts files must not import private module '${specifier.text}'.`, + start, + }); + } + }; + ast.forEachChild((node) => { + if (node.kind === ts.SyntaxKind.ExportDeclaration) { + const exportNode = + /** @type {import('typescript').ExportDeclaration} */ (node); + checkSpecifier(exportNode.moduleSpecifier); + } else if (node.kind === ts.SyntaxKind.ImportDeclaration) { + const importNode = + /** @type {import('typescript').ImportDeclaration} */ (node); + checkSpecifier(importNode.moduleSpecifier); + } + }); + } + if (diagnostics.length > 0) { + const msg = ( + pretty ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics + )(diagnostics, diagnosticsHost); + console.error(msg.replace(/ TSInfinity:/g, ':')); + process.exit(1); + } +} + +validateTscTypes();