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

chore(typescript): refactor compiler host #214

Merged
merged 1 commit into from
Feb 20, 2020
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
34 changes: 34 additions & 0 deletions packages/typescript/src/diagnostics/emit.ts
Original file line number Diff line number Diff line change
@@ -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);
}
});
}
38 changes: 38 additions & 0 deletions packages/typescript/src/diagnostics/host.ts
Original file line number Diff line number Diff line change
@@ -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()
};
}
Original file line number Diff line number Diff line change
@@ -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<import('typescript').LanguageServiceHost, 'getCompilationSettings'>,
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
Expand Down
39 changes: 10 additions & 29 deletions packages/typescript/src/host.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
16 changes: 7 additions & 9 deletions packages/typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,27 @@ 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';

export default function typescript(options: RollupTypescriptOptions = {}): Plugin {
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()));

return {
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) {
Expand Down Expand Up @@ -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}`);
}
Expand All @@ -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));
}
};
}
19 changes: 19 additions & 0 deletions packages/typescript/src/moduleResolution/host.ts
Original file line number Diff line number Diff line change
@@ -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
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type ModuleResolverHost = import('typescript').ModuleResolutionHost &
Pick<import('typescript').FormatDiagnosticsHost, 'getCanonicalFileName'> &
Pick<import('typescript').LanguageServiceHost, 'getCompilationSettings'>;
import { DiagnosticsHost } from '../diagnostics/host';

type ModuleResolutionHost = import('typescript').ModuleResolutionHost;
type ModuleResolverHost = ModuleResolutionHost & DiagnosticsHost;

export type Resolver = (
moduleName: string,
Expand All @@ -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'),
Expand Down
19 changes: 18 additions & 1 deletion packages/typescript/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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`);
}
}
4 changes: 1 addition & 3 deletions packages/typescript/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down