Skip to content

Commit 0fdde33

Browse files
authored
chore(typescript): refactor compiler host (#214)
1 parent a72d189 commit 0fdde33

File tree

9 files changed

+135
-78
lines changed

9 files changed

+135
-78
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { PluginContext } from 'rollup';
2+
3+
import { DiagnosticsHost } from './host';
4+
import diagnosticToWarning from './toWarning';
5+
6+
// `Cannot compile modules into 'es6' when targeting 'ES5' or lower.`
7+
const CANNOT_COMPILE_ESM = 1204;
8+
9+
/**
10+
* For each type error reported by Typescript, emit a Rollup warning or error.
11+
*/
12+
export default function emitDiagnostics(
13+
ts: typeof import('typescript'),
14+
context: PluginContext,
15+
host: DiagnosticsHost,
16+
diagnostics: readonly import('typescript').Diagnostic[] | undefined
17+
) {
18+
if (!diagnostics) return;
19+
const { noEmitOnError } = host.getCompilationSettings();
20+
21+
diagnostics
22+
.filter((diagnostic) => diagnostic.code !== CANNOT_COMPILE_ESM)
23+
.forEach((diagnostic) => {
24+
// Build a Rollup warning object from the diagnostics object.
25+
const warning = diagnosticToWarning(ts, host, diagnostic);
26+
27+
// Errors are fatal. Otherwise emit warnings.
28+
if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) {
29+
context.error(warning);
30+
} else {
31+
context.warn(warning);
32+
}
33+
});
34+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
type FormatDiagnosticsHost = import('typescript').FormatDiagnosticsHost;
2+
3+
export interface DiagnosticsHost extends FormatDiagnosticsHost {
4+
getCompilationSettings(): import('typescript').CompilerOptions;
5+
}
6+
7+
/**
8+
* Create a format diagnostics host to use with the Typescript type checking APIs.
9+
* Typescript hosts are used to represent the user's system,
10+
* with an API for checking case sensitivity etc.
11+
* @param compilerOptions Typescript compiler options. Affects functions such as `getNewLine`.
12+
* @see https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
13+
*/
14+
export default function createFormattingHost(
15+
ts: typeof import('typescript'),
16+
compilerOptions: import('typescript').CompilerOptions
17+
): DiagnosticsHost {
18+
return {
19+
/** Returns the compiler options for the project. */
20+
getCompilationSettings: () => compilerOptions,
21+
/** Returns the current working directory. */
22+
getCurrentDirectory: () => process.cwd(),
23+
/** Returns the string that corresponds with the selected `NewLineKind`. */
24+
getNewLine() {
25+
switch (compilerOptions.newLine) {
26+
case ts.NewLineKind.CarriageReturnLineFeed:
27+
return '\r\n';
28+
case ts.NewLineKind.LineFeed:
29+
return '\n';
30+
default:
31+
return ts.sys.newLine;
32+
}
33+
},
34+
/** Returns a lower case name on case insensitive systems, otherwise the original name. */
35+
getCanonicalFileName: (fileName) =>
36+
ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase()
37+
};
38+
}

packages/typescript/src/diagnostics.ts renamed to packages/typescript/src/diagnostics/toWarning.ts

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,9 @@
1-
import { PluginContext, RollupLogProps } from 'rollup';
2-
3-
// `Cannot compile modules into 'es6' when targeting 'ES5' or lower.`
4-
const CANNOT_COMPILE_ESM = 1204;
5-
6-
/**
7-
* For each type error reported by Typescript, emit a Rollup warning or error.
8-
*/
9-
export function emitDiagnostics(
10-
ts: typeof import('typescript'),
11-
context: PluginContext,
12-
host: import('typescript').FormatDiagnosticsHost &
13-
Pick<import('typescript').LanguageServiceHost, 'getCompilationSettings'>,
14-
diagnostics: readonly import('typescript').Diagnostic[] | undefined
15-
) {
16-
if (!diagnostics) return;
17-
const { noEmitOnError } = host.getCompilationSettings();
18-
19-
diagnostics
20-
.filter((diagnostic) => diagnostic.code !== CANNOT_COMPILE_ESM)
21-
.forEach((diagnostic) => {
22-
// Build a Rollup warning object from the diagnostics object.
23-
const warning = diagnosticToWarning(ts, host, diagnostic);
24-
25-
// Errors are fatal. Otherwise emit warnings.
26-
if (noEmitOnError && diagnostic.category === ts.DiagnosticCategory.Error) {
27-
context.error(warning);
28-
} else {
29-
context.warn(warning);
30-
}
31-
});
32-
}
1+
import { RollupLogProps } from 'rollup';
332

343
/**
354
* Converts a Typescript type error into an equivalent Rollup warning object.
365
*/
37-
export function diagnosticToWarning(
6+
export default function diagnosticToWarning(
387
ts: typeof import('typescript'),
398
host: import('typescript').FormatDiagnosticsHost | null,
409
diagnostic: import('typescript').Diagnostic

packages/typescript/src/host.ts

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import createModuleResolver, { Resolver } from './resolver';
1+
import createFormattingHost, { DiagnosticsHost } from './diagnostics/host';
2+
import createModuleResolutionHost, { ModuleResolutionHost } from './moduleResolution/host';
3+
import createModuleResolver, { Resolver } from './moduleResolution/resolver';
24

3-
type BaseHost = import('typescript').LanguageServiceHost &
4-
import('typescript').ModuleResolutionHost &
5-
import('typescript').FormatDiagnosticsHost;
5+
type BaseHost = import('typescript').LanguageServiceHost & ModuleResolutionHost & DiagnosticsHost;
66

77
export interface TypescriptHost extends BaseHost {
88
/**
@@ -35,6 +35,10 @@ interface File {
3535

3636
/**
3737
* Create a language service host to use with the Typescript compiler & type checking APIs.
38+
* Typescript hosts are used to represent the user's system,
39+
* with an API for reading files, checking directories and case sensitivity etc.
40+
* This host creates a local file cache which can be updated with `addFile`.
41+
*
3842
* @param parsedOptions Parsed options for Typescript.
3943
* @param parsedOptions.options Typescript compiler options. Affects functions such as `getNewLine`.
4044
* @param parsedOptions.fileNames Declaration files to include for typechecking.
@@ -79,16 +83,10 @@ export default function createHost(
7983

8084
let resolver: Resolver;
8185
const host: TypescriptHost = {
82-
getCompilationSettings: () => parsedOptions.options,
83-
getCurrentDirectory: () => process.cwd(),
84-
getNewLine: () => getNewLine(ts, parsedOptions.options.newLine),
85-
getCanonicalFileName: (fileName) =>
86-
ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
86+
...createModuleResolutionHost(ts),
87+
...createFormattingHost(ts, parsedOptions.options),
8788
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
8889
getDefaultLibFileName: ts.getDefaultLibFilePath,
89-
getDirectories: ts.sys.getDirectories,
90-
directoryExists: ts.sys.directoryExists,
91-
realpath: ts.sys.realpath,
9290
readDirectory: ts.sys.readDirectory,
9391
readFile(fileName, encoding) {
9492
const file = files.get(fileName);
@@ -109,20 +107,3 @@ export default function createHost(
109107

110108
return host;
111109
}
112-
113-
/**
114-
* Returns the string that corresponds with the selected `NewLineKind`.
115-
*/
116-
function getNewLine(
117-
ts: typeof import('typescript'),
118-
kind: import('typescript').NewLineKind | undefined
119-
) {
120-
switch (kind) {
121-
case ts.NewLineKind.CarriageReturnLineFeed:
122-
return '\r\n';
123-
case ts.NewLineKind.LineFeed:
124-
return '\n';
125-
default:
126-
return ts.sys.newLine;
127-
}
128-
}

packages/typescript/src/index.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,27 @@ import { Plugin } from 'rollup';
44

55
import { RollupTypescriptOptions } from '../types';
66

7-
import { diagnosticToWarning, emitDiagnostics } from './diagnostics';
7+
import emitDiagnostics from './diagnostics/emit';
8+
import createFormattingHost from './diagnostics/host';
89
import getDocumentRegistry from './documentRegistry';
910
import createHost from './host';
10-
import { getPluginOptions, parseTypescriptConfig } from './options';
11+
import { emitParsedOptionsErrors, getPluginOptions, parseTypescriptConfig } from './options';
1112
import typescriptOutputToRollupTransformation from './outputToRollupTransformation';
1213
import { TSLIB_ID } from './tslib';
1314

1415
export default function typescript(options: RollupTypescriptOptions = {}): Plugin {
1516
const { filter, tsconfig, compilerOptions, tslib, typescript: ts } = getPluginOptions(options);
1617

1718
const parsedOptions = parseTypescriptConfig(ts, tsconfig, compilerOptions);
19+
const formatHost = createFormattingHost(ts, parsedOptions.options);
1820
const host = createHost(ts, parsedOptions);
1921
const services = ts.createLanguageService(host, getDocumentRegistry(ts, process.cwd()));
2022

2123
return {
2224
name: 'typescript',
2325

2426
buildStart() {
25-
if (parsedOptions.errors.length > 0) {
26-
parsedOptions.errors.forEach((error) => this.warn(diagnosticToWarning(ts, host, error)));
27-
28-
this.error(`@rollup/plugin-typescript: Couldn't process compiler options`);
29-
}
27+
emitParsedOptionsErrors(ts, this, parsedOptions);
3028
},
3129

3230
resolveId(importee, importer) {
@@ -68,7 +66,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
6866
const allDiagnostics = ([] as import('typescript').Diagnostic[])
6967
.concat(services.getSyntacticDiagnostics(id))
7068
.concat(services.getSemanticDiagnostics(id));
71-
emitDiagnostics(ts, this, host, allDiagnostics);
69+
emitDiagnostics(ts, this, formatHost, allDiagnostics);
7270

7371
throw new Error(`Couldn't compile ${id}`);
7472
}
@@ -79,7 +77,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
7977
generateBundle() {
8078
const program = services.getProgram();
8179
if (program == null) return;
82-
emitDiagnostics(ts, this, host, ts.getPreEmitDiagnostics(program));
80+
emitDiagnostics(ts, this, formatHost, ts.getPreEmitDiagnostics(program));
8381
}
8482
};
8583
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export type ModuleResolutionHost = import('typescript').ModuleResolutionHost;
2+
3+
/**
4+
* Creates a module resolution host to use with the Typescript compiler API.
5+
* Typescript hosts are used to represent the user's system,
6+
* with an API for reading files, checking directories and case sensitivity etc.
7+
* @see https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
8+
*/
9+
export default function createModuleResolutionHost(
10+
ts: typeof import('typescript')
11+
): ModuleResolutionHost {
12+
return {
13+
fileExists: ts.sys.fileExists,
14+
readFile: ts.sys.readFile,
15+
directoryExists: ts.sys.directoryExists,
16+
realpath: ts.sys.realpath,
17+
getDirectories: ts.sys.getDirectories
18+
};
19+
}

packages/typescript/src/resolver.ts renamed to packages/typescript/src/moduleResolution/resolver.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
type ModuleResolverHost = import('typescript').ModuleResolutionHost &
2-
Pick<import('typescript').FormatDiagnosticsHost, 'getCanonicalFileName'> &
3-
Pick<import('typescript').LanguageServiceHost, 'getCompilationSettings'>;
1+
import { DiagnosticsHost } from '../diagnostics/host';
2+
3+
type ModuleResolutionHost = import('typescript').ModuleResolutionHost;
4+
type ModuleResolverHost = ModuleResolutionHost & DiagnosticsHost;
45

56
export type Resolver = (
67
moduleName: string,
@@ -9,6 +10,8 @@ export type Resolver = (
910

1011
/**
1112
* Create a helper for resolving modules using Typescript.
13+
* @param host Typescript host that extends `ModuleResolutionHost`
14+
* with methods for sanitizing filenames and getting compiler options.
1215
*/
1316
export default function createModuleResolver(
1417
ts: typeof import('typescript'),

packages/typescript/src/options.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { readFileSync } from 'fs';
22
import { resolve } from 'path';
33

44
import { createFilter } from '@rollup/pluginutils';
5+
import { PluginContext } from 'rollup';
56
import * as defaultTs from 'typescript';
67

78
import { RollupTypescriptOptions } from '../types';
89

9-
import { diagnosticToWarning } from './diagnostics';
10+
import diagnosticToWarning from './diagnostics/toWarning';
1011
import { getTsLibCode } from './tslib';
1112

1213
/** Properties of `CompilerOptions` that are normally enums */
@@ -240,3 +241,19 @@ export function parseTypescriptConfig(
240241

241242
return parsedConfig;
242243
}
244+
245+
/**
246+
* If errors are detected in the parsed options,
247+
* display all of them as warnings then emit an error.
248+
*/
249+
export function emitParsedOptionsErrors(
250+
ts: typeof import('typescript'),
251+
context: PluginContext,
252+
parsedOptions: import('typescript').ParsedCommandLine
253+
) {
254+
if (parsedOptions.errors.length > 0) {
255+
parsedOptions.errors.forEach((error) => context.warn(diagnosticToWarning(ts, null, error)));
256+
257+
context.error(`@rollup/plugin-typescript: Couldn't process compiler options`);
258+
}
259+
}

packages/typescript/test/test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,7 @@ test.serial('should support extends property with node resolution', async (t) =>
352352

353353
const bundle = await rollup({
354354
input: 'main.tsx',
355-
plugins: [
356-
typescript()
357-
],
355+
plugins: [typescript()],
358356
onwarn
359357
});
360358
const code = await getCode(bundle, outputOptions);

0 commit comments

Comments
 (0)