From 76240adaabbc1886be3d270e6d317616ba160037 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Mon, 17 Feb 2020 21:37:27 -0800 Subject: [PATCH] chore(typescript): refactor compiler host --- packages/typescript/src/diagnostics/emit.ts | 34 ++++++++++++++++ packages/typescript/src/diagnostics/host.ts | 38 ++++++++++++++++++ .../toWarning.ts} | 35 +---------------- packages/typescript/src/host.ts | 39 +++++-------------- packages/typescript/src/index.ts | 16 ++++---- .../typescript/src/moduleResolution/host.ts | 19 +++++++++ .../src/{ => moduleResolution}/resolver.ts | 9 +++-- packages/typescript/src/options.ts | 19 ++++++++- packages/typescript/test/test.js | 4 +- 9 files changed, 135 insertions(+), 78 deletions(-) create mode 100644 packages/typescript/src/diagnostics/emit.ts create mode 100644 packages/typescript/src/diagnostics/host.ts rename packages/typescript/src/{diagnostics.ts => diagnostics/toWarning.ts} (56%) create mode 100644 packages/typescript/src/moduleResolution/host.ts rename packages/typescript/src/{ => moduleResolution}/resolver.ts (68%) diff --git a/packages/typescript/src/diagnostics/emit.ts b/packages/typescript/src/diagnostics/emit.ts new file mode 100644 index 000000000..4350f3837 --- /dev/null +++ b/packages/typescript/src/diagnostics/emit.ts @@ -0,0 +1,34 @@ +import { PluginContext } from 'rollup'; + +import { DiagnosticsHost } from './host'; +import diagnosticToWarning from './toWarning'; + +// `Cannot compile modules into 'es6' when targeting 'ES5' or lower.` +const CANNOT_COMPILE_ESM = 1204; + +/** + * For each type error reported by Typescript, emit a Rollup warning or error. + */ +export default function emitDiagnostics( + ts: typeof import('typescript'), + context: PluginContext, + host: DiagnosticsHost, + diagnostics: readonly import('typescript').Diagnostic[] | undefined +) { + if (!diagnostics) return; + const { noEmitOnError } = host.getCompilationSettings(); + + diagnostics + .filter((diagnostic) => diagnostic.code !== CANNOT_COMPILE_ESM) + .forEach((diagnostic) => { + // Build a Rollup warning object from the diagnostics object. + const warning = diagnosticToWarning(ts, host, diagnostic); + + // Errors are fatal. Otherwise emit warnings. + if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) { + context.error(warning); + } else { + context.warn(warning); + } + }); +} diff --git a/packages/typescript/src/diagnostics/host.ts b/packages/typescript/src/diagnostics/host.ts new file mode 100644 index 000000000..e119959ed --- /dev/null +++ b/packages/typescript/src/diagnostics/host.ts @@ -0,0 +1,38 @@ +type FormatDiagnosticsHost = import('typescript').FormatDiagnosticsHost; + +export interface DiagnosticsHost extends FormatDiagnosticsHost { + getCompilationSettings(): import('typescript').CompilerOptions; +} + +/** + * Create a format diagnostics host to use with the Typescript type checking APIs. + * Typescript hosts are used to represent the user's system, + * with an API for checking case sensitivity etc. + * @param compilerOptions Typescript compiler options. Affects functions such as `getNewLine`. + * @see https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API + */ +export default function createFormattingHost( + ts: typeof import('typescript'), + compilerOptions: import('typescript').CompilerOptions +): DiagnosticsHost { + return { + /** Returns the compiler options for the project. */ + getCompilationSettings: () => compilerOptions, + /** Returns the current working directory. */ + getCurrentDirectory: () => process.cwd(), + /** Returns the string that corresponds with the selected `NewLineKind`. */ + getNewLine() { + switch (compilerOptions.newLine) { + case ts.NewLineKind.CarriageReturnLineFeed: + return '\r\n'; + case ts.NewLineKind.LineFeed: + return '\n'; + default: + return ts.sys.newLine; + } + }, + /** Returns a lower case name on case insensitive systems, otherwise the original name. */ + getCanonicalFileName: (fileName) => + ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase() + }; +} diff --git a/packages/typescript/src/diagnostics.ts b/packages/typescript/src/diagnostics/toWarning.ts similarity index 56% rename from packages/typescript/src/diagnostics.ts rename to packages/typescript/src/diagnostics/toWarning.ts index 89b45ee32..d6850321b 100644 --- a/packages/typescript/src/diagnostics.ts +++ b/packages/typescript/src/diagnostics/toWarning.ts @@ -1,40 +1,9 @@ -import { PluginContext, RollupLogProps } from 'rollup'; - -// `Cannot compile modules into 'es6' when targeting 'ES5' or lower.` -const CANNOT_COMPILE_ESM = 1204; - -/** - * For each type error reported by Typescript, emit a Rollup warning or error. - */ -export function emitDiagnostics( - ts: typeof import('typescript'), - context: PluginContext, - host: import('typescript').FormatDiagnosticsHost & - Pick, - diagnostics: readonly import('typescript').Diagnostic[] | undefined -) { - if (!diagnostics) return; - const { noEmitOnError } = host.getCompilationSettings(); - - diagnostics - .filter((diagnostic) => diagnostic.code !== CANNOT_COMPILE_ESM) - .forEach((diagnostic) => { - // Build a Rollup warning object from the diagnostics object. - const warning = diagnosticToWarning(ts, host, diagnostic); - - // Errors are fatal. Otherwise emit warnings. - if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) { - context.error(warning); - } else { - context.warn(warning); - } - }); -} +import { RollupLogProps } from 'rollup'; /** * Converts a Typescript type error into an equivalent Rollup warning object. */ -export function diagnosticToWarning( +export default function diagnosticToWarning( ts: typeof import('typescript'), host: import('typescript').FormatDiagnosticsHost | null, diagnostic: import('typescript').Diagnostic diff --git a/packages/typescript/src/host.ts b/packages/typescript/src/host.ts index 0c1bcf1ae..fcff1c82c 100644 --- a/packages/typescript/src/host.ts +++ b/packages/typescript/src/host.ts @@ -1,8 +1,8 @@ -import createModuleResolver, { Resolver } from './resolver'; +import createFormattingHost, { DiagnosticsHost } from './diagnostics/host'; +import createModuleResolutionHost, { ModuleResolutionHost } from './moduleResolution/host'; +import createModuleResolver, { Resolver } from './moduleResolution/resolver'; -type BaseHost = import('typescript').LanguageServiceHost & - import('typescript').ModuleResolutionHost & - import('typescript').FormatDiagnosticsHost; +type BaseHost = import('typescript').LanguageServiceHost & ModuleResolutionHost & DiagnosticsHost; export interface TypescriptHost extends BaseHost { /** @@ -35,6 +35,10 @@ interface File { /** * Create a language service host to use with the Typescript compiler & type checking APIs. + * Typescript hosts are used to represent the user's system, + * with an API for reading files, checking directories and case sensitivity etc. + * This host creates a local file cache which can be updated with `addFile`. + * * @param parsedOptions Parsed options for Typescript. * @param parsedOptions.options Typescript compiler options. Affects functions such as `getNewLine`. * @param parsedOptions.fileNames Declaration files to include for typechecking. @@ -79,16 +83,10 @@ export default function createHost( let resolver: Resolver; const host: TypescriptHost = { - getCompilationSettings: () => parsedOptions.options, - getCurrentDirectory: () => process.cwd(), - getNewLine: () => getNewLine(ts, parsedOptions.options.newLine), - getCanonicalFileName: (fileName) => - ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), + ...createModuleResolutionHost(ts), + ...createFormattingHost(ts, parsedOptions.options), useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, getDefaultLibFileName: ts.getDefaultLibFilePath, - getDirectories: ts.sys.getDirectories, - directoryExists: ts.sys.directoryExists, - realpath: ts.sys.realpath, readDirectory: ts.sys.readDirectory, readFile(fileName, encoding) { const file = files.get(fileName); @@ -109,20 +107,3 @@ export default function createHost( return host; } - -/** - * Returns the string that corresponds with the selected `NewLineKind`. - */ -function getNewLine( - ts: typeof import('typescript'), - kind: import('typescript').NewLineKind | undefined -) { - switch (kind) { - case ts.NewLineKind.CarriageReturnLineFeed: - return '\r\n'; - case ts.NewLineKind.LineFeed: - return '\n'; - default: - return ts.sys.newLine; - } -} diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index 04e1470e1..c3685342c 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -4,10 +4,11 @@ import { Plugin } from 'rollup'; import { RollupTypescriptOptions } from '../types'; -import { diagnosticToWarning, emitDiagnostics } from './diagnostics'; +import emitDiagnostics from './diagnostics/emit'; +import createFormattingHost from './diagnostics/host'; import getDocumentRegistry from './documentRegistry'; import createHost from './host'; -import { getPluginOptions, parseTypescriptConfig } from './options'; +import { emitParsedOptionsErrors, getPluginOptions, parseTypescriptConfig } from './options'; import typescriptOutputToRollupTransformation from './outputToRollupTransformation'; import { TSLIB_ID } from './tslib'; @@ -15,6 +16,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi const { filter, tsconfig, compilerOptions, tslib, typescript: ts } = getPluginOptions(options); const parsedOptions = parseTypescriptConfig(ts, tsconfig, compilerOptions); + const formatHost = createFormattingHost(ts, parsedOptions.options); const host = createHost(ts, parsedOptions); const services = ts.createLanguageService(host, getDocumentRegistry(ts, process.cwd())); @@ -22,11 +24,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi name: 'typescript', buildStart() { - if (parsedOptions.errors.length > 0) { - parsedOptions.errors.forEach((error) => this.warn(diagnosticToWarning(ts, host, error))); - - this.error(`@rollup/plugin-typescript: Couldn't process compiler options`); - } + emitParsedOptionsErrors(ts, this, parsedOptions); }, resolveId(importee, importer) { @@ -68,7 +66,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi const allDiagnostics = ([] as import('typescript').Diagnostic[]) .concat(services.getSyntacticDiagnostics(id)) .concat(services.getSemanticDiagnostics(id)); - emitDiagnostics(ts, this, host, allDiagnostics); + emitDiagnostics(ts, this, formatHost, allDiagnostics); throw new Error(`Couldn't compile ${id}`); } @@ -79,7 +77,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi generateBundle() { const program = services.getProgram(); if (program == null) return; - emitDiagnostics(ts, this, host, ts.getPreEmitDiagnostics(program)); + emitDiagnostics(ts, this, formatHost, ts.getPreEmitDiagnostics(program)); } }; } diff --git a/packages/typescript/src/moduleResolution/host.ts b/packages/typescript/src/moduleResolution/host.ts new file mode 100644 index 000000000..81279a688 --- /dev/null +++ b/packages/typescript/src/moduleResolution/host.ts @@ -0,0 +1,19 @@ +export type ModuleResolutionHost = import('typescript').ModuleResolutionHost; + +/** + * Creates a module resolution host to use with the Typescript compiler API. + * Typescript hosts are used to represent the user's system, + * with an API for reading files, checking directories and case sensitivity etc. + * @see https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API + */ +export default function createModuleResolutionHost( + ts: typeof import('typescript') +): ModuleResolutionHost { + return { + fileExists: ts.sys.fileExists, + readFile: ts.sys.readFile, + directoryExists: ts.sys.directoryExists, + realpath: ts.sys.realpath, + getDirectories: ts.sys.getDirectories + }; +} diff --git a/packages/typescript/src/resolver.ts b/packages/typescript/src/moduleResolution/resolver.ts similarity index 68% rename from packages/typescript/src/resolver.ts rename to packages/typescript/src/moduleResolution/resolver.ts index d57740334..282818e02 100644 --- a/packages/typescript/src/resolver.ts +++ b/packages/typescript/src/moduleResolution/resolver.ts @@ -1,6 +1,7 @@ -type ModuleResolverHost = import('typescript').ModuleResolutionHost & - Pick & - Pick; +import { DiagnosticsHost } from '../diagnostics/host'; + +type ModuleResolutionHost = import('typescript').ModuleResolutionHost; +type ModuleResolverHost = ModuleResolutionHost & DiagnosticsHost; export type Resolver = ( moduleName: string, @@ -9,6 +10,8 @@ export type Resolver = ( /** * Create a helper for resolving modules using Typescript. + * @param host Typescript host that extends `ModuleResolutionHost` + * with methods for sanitizing filenames and getting compiler options. */ export default function createModuleResolver( ts: typeof import('typescript'), diff --git a/packages/typescript/src/options.ts b/packages/typescript/src/options.ts index b76cbfc84..ebdb6debd 100644 --- a/packages/typescript/src/options.ts +++ b/packages/typescript/src/options.ts @@ -2,11 +2,12 @@ import { readFileSync } from 'fs'; import { resolve } from 'path'; import { createFilter } from '@rollup/pluginutils'; +import { PluginContext } from 'rollup'; import * as defaultTs from 'typescript'; import { RollupTypescriptOptions } from '../types'; -import { diagnosticToWarning } from './diagnostics'; +import diagnosticToWarning from './diagnostics/toWarning'; import { getTsLibCode } from './tslib'; /** Properties of `CompilerOptions` that are normally enums */ @@ -240,3 +241,19 @@ export function parseTypescriptConfig( return parsedConfig; } + +/** + * If errors are detected in the parsed options, + * display all of them as warnings then emit an error. + */ +export function emitParsedOptionsErrors( + ts: typeof import('typescript'), + context: PluginContext, + parsedOptions: import('typescript').ParsedCommandLine +) { + if (parsedOptions.errors.length > 0) { + parsedOptions.errors.forEach((error) => context.warn(diagnosticToWarning(ts, null, error))); + + context.error(`@rollup/plugin-typescript: Couldn't process compiler options`); + } +} diff --git a/packages/typescript/test/test.js b/packages/typescript/test/test.js index 2deed03bf..bac09bbef 100644 --- a/packages/typescript/test/test.js +++ b/packages/typescript/test/test.js @@ -352,9 +352,7 @@ test.serial('should support extends property with node resolution', async (t) => const bundle = await rollup({ input: 'main.tsx', - plugins: [ - typescript() - ], + plugins: [typescript()], onwarn }); const code = await getCode(bundle, outputOptions);