├── .editorconfig
├── .eslintrc.cjs
├── .gitattributes
├── .github
├── issue_template.md
├── pull_request_template.md
└── workflows
│ └── nodejs.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── __tests__
├── context.spec.ts
├── diagnostics-format-host.spec.ts
├── diagnostics.spec.ts
├── fixtures
│ ├── context.ts
│ └── options.ts
├── get-options-overrides.spec.ts
├── host.spec.ts
├── integration
│ ├── errors.spec.ts
│ ├── fixtures
│ │ ├── errors
│ │ │ ├── import-type-error.ts
│ │ │ ├── semantic.ts
│ │ │ ├── syntax.ts
│ │ │ ├── tsconfig.json
│ │ │ └── type-only-import-with-error.ts
│ │ ├── no-errors.ts
│ │ ├── no-errors
│ │ │ ├── index.ts
│ │ │ ├── some-import.ts
│ │ │ ├── some-js-import.d.ts
│ │ │ ├── some-js-import.js
│ │ │ ├── tsconfig.json
│ │ │ ├── type-only-import-import.ts
│ │ │ └── type-only-import.ts
│ │ └── tsconfig.json
│ ├── helpers.ts
│ ├── no-errors.spec.ts
│ └── watch.spec.ts
├── parse-tsconfig.spec.ts
├── rollingcache.spec.ts
└── tslib.spec.ts
├── dist
├── context.d.ts
├── context.d.ts.map
├── diagnostics-format-host.d.ts
├── diagnostics-format-host.d.ts.map
├── diagnostics.d.ts
├── diagnostics.d.ts.map
├── get-options-overrides.d.ts
├── get-options-overrides.d.ts.map
├── host.d.ts
├── host.d.ts.map
├── icache.d.ts
├── icache.d.ts.map
├── index.d.ts
├── index.d.ts.map
├── ioptions.d.ts
├── ioptions.d.ts.map
├── parse-tsconfig.d.ts
├── parse-tsconfig.d.ts.map
├── rollingcache.d.ts
├── rollingcache.d.ts.map
├── rollup-plugin-typescript2.cjs.js
├── rollup-plugin-typescript2.cjs.js.map
├── rollup-plugin-typescript2.es.js
├── rollup-plugin-typescript2.es.js.map
├── tscache.d.ts
├── tscache.d.ts.map
├── tslib.d.ts
├── tslib.d.ts.map
├── tsproxy.d.ts
└── tsproxy.d.ts.map
├── jest.config.js
├── package-lock.json
├── package.json
├── rollup.config.base.js
├── rollup.config.js
├── rollup.config.self.js
├── src
├── context.ts
├── diagnostics-format-host.ts
├── diagnostics.ts
├── get-options-overrides.ts
├── host.ts
├── icache.ts
├── index.ts
├── ioptions.ts
├── parse-tsconfig.ts
├── rollingcache.ts
├── tscache.ts
├── tslib.ts
└── tsproxy.ts
├── tsconfig.base.json
├── tsconfig.json
└── tsconfig.test.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | indent_style = tab
7 | indent_size = 4
8 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
4 | parser: '@typescript-eslint/parser',
5 | plugins: ['@typescript-eslint'],
6 | root: true,
7 | ignorePatterns: ['dist/**', 'node_modules/**', 'build-self/**', '*.js', "__tests__/integration/fixtures/errors/**"],
8 | rules: {
9 | "@typescript-eslint/no-explicit-any": "off", // these are explicit, so they are intentional
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * -text
2 | dist/* linguist-generated
3 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | ## Troubleshooting
2 |
6 |
7 | 1. Does `tsc` have the same output? If so, please explain why this is incorrect behavior
8 |
13 |
14 | 1. Does your Rollup plugin order match [this plugin's compatibility](https://github.com/ezolenko/rollup-plugin-typescript2#compatibility)? If not, please elaborate
15 |
18 |
19 | 1. Can you create a [minimal example](https://stackoverflow.com/help/minimal-reproducible-example) that reproduces this behavior? Preferably, use [this environment](https://stackblitz.com/edit/rpt2-repro) for your reproduction
20 |
25 |
26 | ## What happens and why it is incorrect
27 |
31 |
32 | ## Environment
33 |
34 |
35 | ### Versions
36 |
43 |
44 | ```text
45 |
46 | ```
47 |
48 |
49 |
50 | rollup.config.js
:
51 |
52 |
53 |
54 | ```js
55 |
56 | ```
57 |
58 |
59 |
60 |
61 |
62 | tsconfig.json
:
63 |
64 |
65 |
66 | ```json5
67 |
68 | ```
69 |
70 |
71 |
72 |
73 |
74 | package.json
:
75 |
76 |
77 |
78 | ```json
79 |
80 | ```
81 |
82 |
83 |
84 |
85 |
86 | plugin output with verbosity 3
:
87 |
88 |
89 |
90 | ```text
91 |
92 | ```
93 |
94 |
95 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 |
7 |
8 | ## Details
9 |
10 |
13 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | lint-build-test:
14 | name: Lint, Build, Test - Node ${{ matrix.node-version }}, ${{ matrix.os }}
15 |
16 | runs-on: ${{ matrix.os }}
17 | strategy:
18 | matrix:
19 | node-version: [18.x, 20.x]
20 | os: [ubuntu-latest, windows-latest, macOS-latest]
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm run lint
31 | - run: npm run build
32 | - run: npm run build-self
33 | - run: npm run build-self
34 | env:
35 | CI: true
36 | - run: npm run test:coverage
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /npm-debug.log
3 | /typings
4 | /.vscode
5 | /.idea
6 | /build-self
7 | /build
8 | .rpt2_cache
9 | *.swp
10 | *.swo
11 | coverage
12 | __temp
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | See release notes on [the GitHub Releases page](https://github.com/ezolenko/rollup-plugin-typescript2/releases).
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Reporting bugs
4 |
5 | Report any bugs [in the GitHub Issue Tracker](https://github.com/ezolenko/rollup-plugin-typescript2/issues).
6 |
7 | Please follow the issue template as closely as possible:
8 |
9 | - Attach your `tsconfig.json`, `package.json` (for versions of dependencies), `rollup.config.js`, and any other pieces of your environment that could influence module resolution, ambient types, and TS compilation.
10 |
11 | Some additional debugging steps you can take to help diagnose the issue:
12 |
13 | - Attach plugin output with `verbosity` option set to `3` (this will list all files being transpiled and their imports).
14 | - If it makes sense, check if running `tsc` directly produces similar results.
15 | - Check if you get the same problem with `clean` option set to `true` (might indicate a bug in the cache).
16 | - Check if the problem is reproducible after running `npm prune` to clear any rogue types from `node_modules` (by default TS grabs _all_ ambient types).
17 |
18 | ## Developing
19 |
20 | Use the [standard GitHub process](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/getting-started/about-collaborative-development-models#fork-and-pull-model) of forking, making a branch, creating a PR when ready, and fixing any failing checks on the PR.
21 |
22 | ### Linting and Style
23 |
24 | 1. Use an editor that supports [`editorconfig`](https://editorconfig.org/), or match the settings from [`.editorconfig`](./.editorconfig) manually.
25 | 1. Fix all linting problems with `npm run lint`.
26 |
27 | ### Testing
28 |
29 | 1. `npm test` to verify that all tests pass
30 | 1. `npm run test:watch` to run tests in watch mode while developing
31 | 1. `npm run test:coverage` to run tests and output a test coverage report
32 |
33 | While this repo now has an assortment of unit tests and integration tests, it still needs more integration tests with various scenarios and expected outcomes.
34 |
35 | ### Building and Self-Build
36 |
37 | One can test changes by doing a self-build; the plugin is part of its own build system.
38 |
39 | 1. make changes
40 | 1. run `npm run build` (uses last released version on npm)
41 | 1. check that you get expected changes in `dist`
42 | 1. run `npm run build-self` (uses fresh local build)
43 | 1. check `dist` for the expected changes
44 | 1. run `npm run build-self` _again_ to make sure plugin built by new version can still build itself
45 |
46 | If `build-self` breaks at some point, fix the problem and restart from the `build` step (a known good copy).
47 |
48 | ## Learning the codebase
49 |
50 | If you're looking to learn more about the codebase, either to contribute or just to educate yourself, this section contains an outline as well as tips and useful resources.
51 | These docs have been written by contributors who have gone through the process of learning the codebase themselves!
52 |
53 | ### General Overview
54 |
55 | Before starting, make sure you're familiar with the [`README`](README.md) in its entirety, as it describes this plugin's options that make up its API surface.
56 | It can also be useful to review some issues and have a "goal" in mind (especially if you're looking to contribute), so that you can focus on understanding how a certain part of the codebase works.
57 |
58 | 1. Can read [`get-options-overrides`](src/get-options-overrides.ts) as a quick intro to the codebase that dives a bit deeper into the `compilerOptions` that this plugin forces.
59 | - The [TSConfig Reference](https://www.typescriptlang.org/tsconfig) can be a helpful resource to understand these options.
60 | 1. Get a _quick_ read-through of [`index`](src/index.ts) (which is actually relatively small), to get a general understanding of this plugin's workflow.
61 | - Rollup's [Plugin docs](https://rollupjs.org/guide/en/#plugins-overview) are _very_ helpful to reference as you're going through, especially if you're not familiar with the Rollup Plugin API.
62 |
63 | ### Deeper Dive
64 |
65 | Once you have some understanding of the codebase's main workflow, you can start to dive deeper into pieces that require more domain knowledge.
66 | A useful resource as you dive deeper are the [unit tests](__tests__/). They're good to look through as you dig into a module to understand how it's used.
67 |
68 | 1. From here, you can start to read more of the modules that integrate with the TypeScript API, such as [`host`](src/host.ts) and [`parse-tsconfig`](src/parse-tsconfig.ts), and maybe how TS is imported in [`tsproxy`](src/tsproxy.ts) and [`tslib`](src/tslib.ts)
69 | - A _very_ useful reference here is the [TypeScript Wiki](https://github.com/microsoft/TypeScript/wiki), which has two main articles that are the basis for most Compiler integrations:
70 | - [Using the Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
71 | - [Using the Language Service API](https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API)
72 | - _NOTE_: These are fairly short and unfortunately leave a lot to be desired... especially when you consider that this plugin is actually one of the simpler integrations out there.
73 | 1. At this point, you may be ready to read the more complicated bits of [`index`](src/index.ts) in detail and see how it interacts with the other modules.
74 | - The [integration tests](__tests__/integration/) could be useful to review at this point as well.
75 | 1. Once you're pretty familiar with `index`, you can dive into some of the cache code in [`tscache`](src/tscache.ts) and [`rollingcache`](src/rollingcache.ts).
76 | 1. And finally, you can see some of the Rollup logging nuances in [`context`](src/context.ts) and then the TS logging nuances in [`diagnostics`](src/diagnostics.ts), and [`diagnostics-format-host`](src/diagnostics-format-host.ts)
77 | - While these are necessary to the implementation, they are fairly ancillary to understanding and working with the codebase.
78 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Eugene Zolenko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rollup-plugin-typescript2
2 |
3 | [](https://npmjs.org/package/rollup-plugin-typescript2)
4 | [](https://npmjs.org/package/rollup-plugin-typescript2)
5 | [](https://github.com/ezolenko/rollup-plugin-typescript2/actions?query=workflow%3A"Node.js+CI")
6 |
7 | Rollup plugin for typescript with compiler errors.
8 |
9 | This is a rewrite of the [original](https://github.com/rollup/rollup-plugin-typescript/tree/v0.8.1) `rollup-plugin-typescript`, starting and borrowing from [this fork](https://github.com/alexlur/rollup-plugin-typescript).
10 |
11 | This version is somewhat slower than the original, but it will print out TypeScript syntactic and semantic diagnostic messages (the main reason for using TypeScript after all).
12 |
13 | ## Installation
14 |
15 | ```bash
16 | # with npm
17 | npm install rollup-plugin-typescript2 typescript tslib --save-dev
18 | # with yarn
19 | yarn add rollup-plugin-typescript2 typescript tslib --dev
20 | ```
21 |
22 | ## Usage
23 |
24 | ```js
25 | // rollup.config.js
26 | import typescript from 'rollup-plugin-typescript2';
27 |
28 | export default {
29 | input: './main.ts',
30 |
31 | plugins: [
32 | typescript(/*{ plugin options }*/)
33 | ]
34 | }
35 | ```
36 |
37 | This plugin inherits all compiler options and file lists from your `tsconfig.json` file.
38 | If your `tsconfig` has another name or another relative path from the root directory, see `tsconfigDefaults`, `tsconfig`, and `tsconfigOverride` options below.
39 | This also allows for passing in different `tsconfig` files depending on your build target.
40 |
41 | ### Some compiler options are forced
42 |
43 | * `noEmitHelpers`: false
44 | * `importHelpers`: true
45 | * `noResolve`: false
46 | * `noEmit`: false (Rollup controls emit)
47 | * `noEmitOnError`: false (Rollup controls emit. See [#254](https://github.com/ezolenko/rollup-plugin-typescript2/issues/254) and the `abortOnError` plugin option below)
48 | * `inlineSourceMap`: false (see [#71](https://github.com/ezolenko/rollup-plugin-typescript2/issues/71))
49 | * `outDir`: `./placeholder` in cache root (see [#83](https://github.com/ezolenko/rollup-plugin-typescript2/issues/83) and [Microsoft/TypeScript#24715](https://github.com/Microsoft/TypeScript/issues/24715))
50 | * `declarationDir`: Rollup's `output.file` or `output.dir` (*unless `useTsconfigDeclarationDir` is true in the plugin options*)
51 | * `allowNonTsExtensions`: true to let other plugins on the chain generate typescript; update plugin's `include` filter to pick them up (see [#111](https://github.com/ezolenko/rollup-plugin-typescript2/issues/111))
52 |
53 | ### Some compiler options have more than one compatible value
54 |
55 | * `module`: defaults to `ES2015`. Other valid values are `ES2020`, `ES2022` and `ESNext` (required for dynamic imports, see [#54](https://github.com/ezolenko/rollup-plugin-typescript2/issues/54)).
56 |
57 | * `moduleResolution`: defaults to `node10` (same as `node`), but value from tsconfig is used if specified. Other valid (but mostly untested) values are `node16`, `nodenext` and `bundler`. If in doubt, use `node10`.
58 | * `classic` is [deprecated](https://www.typescriptlang.org/docs/handbook/module-resolution.html) and changed to `node10`. It also breaks this plugin, see [#12](https://github.com/ezolenko/rollup-plugin-typescript2/issues/12) and [#14](https://github.com/ezolenko/rollup-plugin-typescript2/issues/14).
59 |
60 | ### Some options need additional configuration on plugin side
61 |
62 | * `allowJs`: lets TypeScript process JS files as well. If you use it, modify this plugin's `include` option to add `"*.js+(|x)", "**/*.js+(|x)"` (might also want to `exclude` `"**/node_modules/**/*"`, as it can slow down the build significantly).
63 |
64 | ### Compatibility
65 |
66 | #### @rollup/plugin-node-resolve
67 |
68 | Must be before `rollup-plugin-typescript2` in the plugin list, especially when the `browser: true` option is used (see [#66](https://github.com/ezolenko/rollup-plugin-typescript2/issues/66)).
69 |
70 | #### @rollup/plugin-commonjs
71 |
72 | See the explanation for `rollupCommonJSResolveHack` option below.
73 |
74 | #### @rollup/plugin-babel
75 |
76 | This plugin transpiles code, but doesn't change file extensions. `@rollup/plugin-babel` only looks at code with these extensions [by default](https://github.com/rollup/plugins/tree/master/packages/babel#extensions): `.js,.jsx,.es6,.es,.mjs`. To workaround this, add `.ts` and `.tsx` to its list of extensions.
77 |
78 | ```js
79 | // ...
80 | import { DEFAULT_EXTENSIONS } from '@babel/core';
81 | // ...
82 | babel({
83 | extensions: [
84 | ...DEFAULT_EXTENSIONS,
85 | '.ts',
86 | '.tsx'
87 | ]
88 | }),
89 | // ...
90 | ```
91 |
92 | See [#108](https://github.com/ezolenko/rollup-plugin-typescript2/issues/108)
93 |
94 | ### Plugin options
95 |
96 | * `cwd`: `string`
97 |
98 | The current working directory. Defaults to `process.cwd()`.
99 |
100 | * `tsconfigDefaults`: `{}`
101 |
102 | The object passed as `tsconfigDefaults` will be merged with the loaded `tsconfig.json`.
103 | The final config passed to TypeScript will be the result of values in `tsconfigDefaults` replaced by values in the loaded `tsconfig.json`, replaced by values in `tsconfigOverride`, and then replaced by forced `compilerOptions` overrides on top of that (see above).
104 |
105 | For simplicity and other tools' sake, try to minimize the usage of defaults and overrides and keep everything in a `tsconfig.json` file (`tsconfig`s can themselves be chained with [`extends`](https://www.typescriptlang.org/tsconfig#extends), so save some turtles).
106 |
107 | ```js
108 | let defaults = { compilerOptions: { declaration: true } };
109 | let override = { compilerOptions: { declaration: false } };
110 |
111 | // ...
112 | plugins: [
113 | typescript({
114 | tsconfigDefaults: defaults,
115 | tsconfig: "tsconfig.json",
116 | tsconfigOverride: override
117 | })
118 | ]
119 | ```
120 |
121 | This is a [deep merge](https://lodash.com/docs/4.17.4#merge): objects are merged, arrays are merged by index, primitives are replaced, etc.
122 | Increase `verbosity` to `3` and look for `parsed tsconfig` if you get something unexpected.
123 |
124 | * `tsconfig`: `undefined`
125 |
126 | Path to `tsconfig.json`.
127 | Set this if your `tsconfig` has another name or relative location from the project directory.
128 |
129 | By default, will try to load `./tsconfig.json`, but will not fail if the file is missing, unless the value is explicitly set.
130 |
131 | * `tsconfigOverride`: `{}`
132 |
133 | See `tsconfigDefaults`.
134 |
135 | * `check`: true
136 |
137 | Set to false to avoid doing any diagnostic checks on the code.
138 | Setting to false is sometimes referred to as `transpileOnly` by other TypeScript integrations.
139 |
140 | * `verbosity`: 1
141 |
142 | - 0 -- Error
143 | - 1 -- Warning
144 | - 2 -- Info
145 | - 3 -- Debug
146 |
147 | * `clean`: false
148 |
149 | Set to true to disable the cache and do a clean build.
150 | This also wipes any existing cache.
151 |
152 | * `cacheRoot`: `node_modules/.cache/rollup-plugin-typescript2`
153 |
154 | Path to cache.
155 | Defaults to a folder in `node_modules`.
156 |
157 | * `include`: `[ "*.ts+(|x)", "**/*.ts+(|x)", "**/*.cts", "**/*.mts" ]`
158 |
159 | By default compiles all `.ts` and `.tsx` files with TypeScript.
160 |
161 | * `exclude`: `[ "*.d.ts", "**/*.d.ts", "**/*.d.cts", "**/*.d.mts" ]`
162 |
163 | But excludes type definitions.
164 |
165 | * `abortOnError`: true
166 |
167 | Bail out on first syntactic or semantic error.
168 | In some cases, setting this to false will result in an exception in Rollup itself (for example, unresolvable imports).
169 |
170 | * `rollupCommonJSResolveHack`: false
171 |
172 | _Deprecated_. OS native paths are now _always_ used since [`0.30.0`](https://github.com/ezolenko/rollup-plugin-typescript2/releases/0.30.0) (see [#251](https://github.com/ezolenko/rollup-plugin-typescript2/pull/251)), so this no longer has any effect -- as if it is always `true`.
173 |
174 | * `objectHashIgnoreUnknownHack`: false
175 |
176 | The plugin uses your Rollup config as part of its cache key.
177 | `object-hash` is used to generate a hash, but it can have trouble with some uncommon types of elements.
178 | Setting this option to true will make `object-hash` ignore unknowns, at the cost of not invalidating the cache if ignored elements are changed.
179 |
180 | Only enable this option if you need it (e.g. if you get `Error: Unknown object type "xxx"`) and make sure to run with `clean: true` once in a while and definitely before a release.
181 | (See [#105](https://github.com/ezolenko/rollup-plugin-typescript2/issues/105) and [#203](https://github.com/ezolenko/rollup-plugin-typescript2/pull/203))
182 |
183 | * `useTsconfigDeclarationDir`: false
184 |
185 | If true, declaration files will be emitted in the [`declarationDir`](https://www.typescriptlang.org/tsconfig#declarationDir) given in the `tsconfig`.
186 | If false, declaration files will be placed inside the destination directory given in the Rollup configuration.
187 |
188 | Set to false if any other Rollup plugins need access to declaration files.
189 |
190 | * `typescript`: peerDependency
191 |
192 | If you'd like to use a different version of TS than the peerDependency, you can import a different TypeScript module and pass it in as `typescript: require("path/to/other/typescript")`.
193 |
194 | You can also use an alternative TypeScript implementation, such as [`ttypescript`](https://github.com/cevek/ttypescript), with this option.
195 |
196 | Must be TS 2.0+; things might break if the compiler interfaces changed enough from what the plugin was built against.
197 |
198 | * `transformers`: `undefined`
199 |
200 | **experimental**, TypeScript 2.4.1+
201 |
202 | Transformers will likely be available in `tsconfig` eventually, so this is not a stable interface (see [Microsoft/TypeScript#14419](https://github.com/Microsoft/TypeScript/issues/14419)).
203 |
204 | For example, integrating [kimamula/ts-transformer-keys](https://github.com/kimamula/ts-transformer-keys):
205 |
206 | ```js
207 | const keysTransformer = require('ts-transformer-keys/transformer').default;
208 | const transformer = (service) => ({
209 | before: [ keysTransformer(service.getProgram()) ],
210 | after: []
211 | });
212 |
213 | // ...
214 | plugins: [
215 | typescript({ transformers: [transformer] })
216 | ]
217 | ```
218 |
219 | ### Declarations
220 |
221 | This plugin respects [`declaration: true`](https://www.typescriptlang.org/tsconfig#declaration) in your `tsconfig.json` file.
222 | When set, it will emit `*.d.ts` files for your bundle.
223 | The resulting file(s) can then be used with the `types` property in your `package.json` file as described [here](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html).
224 | By default, the declaration files will be located in the same directory as the generated Rollup bundle.
225 | If you want to override this behavior and instead use [`declarationDir`](https://www.typescriptlang.org/tsconfig#declarationDir), set `useTsconfigDeclarationDir: true` in the plugin options.
226 |
227 | The above also applies to [`declarationMap: true`](https://www.typescriptlang.org/tsconfig#declarationMap) and `*.d.ts.map` files for your bundle.
228 |
229 | This plugin also respects [`emitDeclarationOnly: true`](https://www.typescriptlang.org/tsconfig#emitDeclarationOnly) and will only emit declarations (and declaration maps, if enabled) if set in your `tsconfig.json`.
230 | If you use `emitDeclarationOnly`, you will need another plugin to compile any TypeScript sources, such as `@rollup/plugin-babel`, `rollup-plugin-esbuild`, `rollup-plugin-swc`, etc.
231 | When composing Rollup plugins this way, `rollup-plugin-typescript2` will perform type-checking and declaration generation, while another plugin performs the TypeScript to JavaScript compilation.
232 | Some scenarios where this can be particularly useful: you want to use Babel plugins on TypeScript source, or you want declarations and type-checking for your Vite builds (**NOTE**: this space has not been fully explored yet).
233 |
234 | ### Watch mode
235 |
236 | The way TypeScript handles type-only imports and ambient types effectively hides them from Rollup's watch mode, because import statements are not generated and changing them doesn't trigger a rebuild.
237 |
238 | Otherwise the plugin should work in watch mode. Make sure to run a normal build after watch session to catch any type errors.
239 |
240 | ### Requirements
241 |
242 | * TypeScript `2.4+`
243 | * Rollup `1.26.3+`
244 | * Node `6.4.0+` (basic ES6 support)
245 |
246 | ### Reporting bugs and Contributing
247 |
248 | See [CONTRIBUTING.md](./CONTRIBUTING.md)
249 |
--------------------------------------------------------------------------------
/__tests__/context.spec.ts:
--------------------------------------------------------------------------------
1 | import { jest, test, expect } from "@jest/globals";
2 |
3 | import { makeContext } from "./fixtures/context";
4 | import { RollupContext, VerbosityLevel } from "../src/context";
5 |
6 | (global as any).console = {
7 | warn: jest.fn(),
8 | log: jest.fn(),
9 | info: jest.fn(),
10 | };
11 |
12 | test("RollupContext", () => {
13 | const innerContext = makeContext();
14 | const context = new RollupContext(5 as VerbosityLevel, false, innerContext);
15 |
16 | context.warn("test");
17 | expect(innerContext.warn).toHaveBeenLastCalledWith("test");
18 |
19 | context.warn(() => "test2");
20 | expect(innerContext.warn).toHaveBeenLastCalledWith("test2");
21 |
22 | context.error("test!");
23 | expect(innerContext.warn).toHaveBeenLastCalledWith("test!");
24 |
25 | context.error(() => "test2!");
26 | expect(innerContext.warn).toHaveBeenLastCalledWith("test2!");
27 |
28 | context.info("test3");
29 | expect(console.log).toHaveBeenLastCalledWith("test3");
30 |
31 | context.info(() => "test4");
32 | expect(console.log).toHaveBeenLastCalledWith("test4");
33 |
34 | context.debug("test5");
35 | expect(console.log).toHaveBeenLastCalledWith("test5");
36 |
37 | context.debug(() => "test6");
38 | expect(console.log).toHaveBeenLastCalledWith("test6");
39 | });
40 |
41 | test("RollupContext with 0 verbosity", () => {
42 | const innerContext = makeContext();
43 | const context = new RollupContext(VerbosityLevel.Error, false, innerContext);
44 |
45 | context.debug("verbosity is too low here");
46 | expect(innerContext.debug).not.toBeCalled();
47 | context.info("verbosity is too low here");
48 | expect(innerContext.debug).not.toBeCalled();
49 | context.warn("verbosity is too low here")
50 | expect(innerContext.warn).not.toBeCalled();
51 | });
52 |
53 | test("RollupContext.error + debug negative verbosity", () => {
54 | const innerContext = makeContext();
55 | const context = new RollupContext(-100 as VerbosityLevel, true, innerContext);
56 |
57 | context.error("verbosity is too low here");
58 | expect(innerContext.error).not.toBeCalled();
59 | context.debug("verbosity is too low here");
60 | expect(innerContext.debug).not.toBeCalled();
61 | });
62 |
63 | test("RollupContext.error with bail", () => {
64 | const innerContext = makeContext();
65 | const context = new RollupContext(5 as VerbosityLevel, true, innerContext);
66 |
67 | context.error("bail");
68 | expect(innerContext.error).toHaveBeenLastCalledWith("bail");
69 | });
70 |
--------------------------------------------------------------------------------
/__tests__/diagnostics-format-host.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@jest/globals";
2 | import * as ts from "typescript";
3 |
4 | import { setTypescriptModule } from "../src/tsproxy";
5 | import { formatHost } from "../src/diagnostics-format-host";
6 |
7 | setTypescriptModule(ts);
8 |
9 | test("formatHost", () => {
10 | expect(formatHost.getCurrentDirectory()).toEqual(process.cwd());
11 | expect(formatHost.getCanonicalFileName("package.json")).toEqual("package.json");
12 | expect(formatHost.getNewLine()).toEqual(ts.sys.newLine);
13 | });
14 |
--------------------------------------------------------------------------------
/__tests__/diagnostics.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@jest/globals";
2 | import * as ts from "typescript";
3 | import { red } from "colors/safe";
4 |
5 | import { makeContext } from "./fixtures/context";
6 | import { setTypescriptModule } from "../src/tsproxy";
7 | import { formatHost } from "../src/diagnostics-format-host";
8 | import { convertDiagnostic, printDiagnostics } from "../src/diagnostics";
9 |
10 | setTypescriptModule(ts);
11 |
12 | const tsDiagnostic = {
13 | file: undefined,
14 | start: undefined,
15 | length: undefined,
16 | messageText: "Compiler option 'include' requires a value of type Array.",
17 | category: ts.DiagnosticCategory.Error,
18 | code: 5024,
19 | reportsUnnecessary: undefined,
20 | reportsDeprecated: undefined,
21 | };
22 |
23 | const diagnostic = {
24 | flatMessage: "Compiler option 'include' requires a value of type Array.",
25 | formatted: `\x1B[91merror\x1B[0m\x1B[90m TS5024: \x1B[0mCompiler option 'include' requires a value of type Array.${formatHost.getNewLine()}`,
26 | category: ts.DiagnosticCategory.Error,
27 | code: 5024,
28 | type: "config",
29 | };
30 |
31 | test("convertDiagnostic", () => {
32 | expect(convertDiagnostic("config", [tsDiagnostic])).toStrictEqual([diagnostic]);
33 | });
34 |
35 | test("printDiagnostics - categories", () => {
36 | const context = makeContext();
37 |
38 | printDiagnostics(context, [diagnostic]);
39 | expect(context.error).toHaveBeenLastCalledWith(diagnostic.formatted);
40 |
41 | printDiagnostics(context, [{ ...diagnostic, category: ts.DiagnosticCategory.Warning } ]);
42 | expect(context.warn).toHaveBeenLastCalledWith(diagnostic.formatted);
43 |
44 | printDiagnostics(context, [{ ...diagnostic, category: ts.DiagnosticCategory.Suggestion } ]); // default case
45 | expect(context.warn).toHaveBeenLastCalledWith(diagnostic.formatted);
46 |
47 | printDiagnostics(context, [{ ...diagnostic, category: ts.DiagnosticCategory.Message } ]);
48 | expect(context.info).toHaveBeenLastCalledWith(diagnostic.formatted);
49 |
50 | // should match exactly, no more
51 | expect(context.error).toBeCalledTimes(1);
52 | expect(context.warn).toBeCalledTimes(2)
53 | expect(context.info).toBeCalledTimes(1);
54 | expect(context.debug).toBeCalledTimes(0);
55 | });
56 |
57 | test("printDiagnostics - formatting / style", () => {
58 | const context = makeContext();
59 | const category = "error"; // string version
60 |
61 | printDiagnostics(context, [diagnostic], false);
62 | expect(context.error).toHaveBeenLastCalledWith(`${diagnostic.type} ${category} TS${diagnostic.code}: ${red(diagnostic.flatMessage)}`);
63 |
64 | const fileLine = "0"
65 | printDiagnostics(context, [{ ...diagnostic, fileLine }], false);
66 | expect(context.error).toHaveBeenLastCalledWith(`${fileLine}: ${diagnostic.type} ${category} TS${diagnostic.code}: ${red(diagnostic.flatMessage)}`);
67 |
68 | // should match exactly, no more
69 | expect(context.error).toBeCalledTimes(2);
70 | expect(context.warn).toBeCalledTimes(0)
71 | expect(context.info).toBeCalledTimes(0);
72 | expect(context.debug).toBeCalledTimes(0);
73 | });
74 |
--------------------------------------------------------------------------------
/__tests__/fixtures/context.ts:
--------------------------------------------------------------------------------
1 | import { jest } from "@jest/globals";
2 | import { PluginContext } from "rollup";
3 |
4 | import { RollupContext } from "../../src/context";
5 |
6 | // if given a function, make sure to call it (for code coverage etc)
7 | function returnText (message: string | (() => string)) {
8 | if (typeof message === "string")
9 | return message;
10 |
11 | return message();
12 | }
13 |
14 | export function makeContext(): PluginContext & RollupContext {
15 | return {
16 | error: jest.fn(returnText),
17 | warn: jest.fn(returnText),
18 | info: jest.fn(returnText),
19 | debug: jest.fn(returnText),
20 | } as unknown as PluginContext & RollupContext;
21 | }
22 |
--------------------------------------------------------------------------------
/__tests__/fixtures/options.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript";
2 |
3 | import { setTypescriptModule } from "../../src/tsproxy";
4 | import { IOptions } from "../../src/ioptions";
5 |
6 | setTypescriptModule(ts);
7 |
8 | export function makeOptions(cacheDir: string, cwd: string): IOptions {
9 | return {
10 | include: ["*.ts+(|x)", "**/*.ts+(|x)"],
11 | exclude: ["*.d.ts", "**/*.d.ts"],
12 | check: false,
13 | verbosity: 5,
14 | clean: false,
15 | cacheRoot: cacheDir,
16 | cwd,
17 | abortOnError: false,
18 | rollupCommonJSResolveHack: false,
19 | typescript: ts,
20 | objectHashIgnoreUnknownHack: false,
21 | tsconfigOverride: null,
22 | useTsconfigDeclarationDir: false,
23 | tsconfigDefaults: null,
24 | sourceMapCallback: (id: string, map: string): void => {
25 | console.log(id + map);
26 | },
27 | transformers: [(ls: ts.LanguageService) => {
28 | console.log(ls);
29 | return {};
30 | }],
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/__tests__/get-options-overrides.spec.ts:
--------------------------------------------------------------------------------
1 | import { afterAll, test, expect } from "@jest/globals";
2 | import * as path from "path";
3 | import * as ts from "typescript";
4 | import { normalizePath as normalize } from "@rollup/pluginutils";
5 | import { remove } from "fs-extra";
6 |
7 | import { makeOptions } from "./fixtures/options";
8 | import { makeContext } from "./fixtures/context";
9 | import { getOptionsOverrides, createFilter } from "../src/get-options-overrides";
10 |
11 | const local = (x: string) => normalize(path.resolve(__dirname, x));
12 | const cacheDir = local("__temp/get-options-overrides");
13 |
14 | // filter expects an absolute path and resolves include/exclude to process.cwd() by default: https://github.com/ezolenko/rollup-plugin-typescript2/pull/321#discussion_r873077874
15 | const filtPath = (relPath: string) => normalize(`${process.cwd()}/${relPath}`);
16 |
17 | afterAll(() => remove(cacheDir));
18 |
19 | const defaultConfig = makeOptions(cacheDir, local(""));
20 |
21 | const forcedOptions: ts.CompilerOptions = {
22 | allowNonTsExtensions: true,
23 | importHelpers: true,
24 | inlineSourceMap: false,
25 | noEmit: false,
26 | noEmitOnError: false,
27 | noEmitHelpers: false,
28 | noResolve: false,
29 | outDir: `${cacheDir}/placeholder`,
30 | };
31 |
32 | const defaultPreParsedTsConfig: ts.ParsedCommandLine = {
33 | options: {},
34 | fileNames: [],
35 | errors: [],
36 | };
37 |
38 | test("getOptionsOverrides", () => {
39 | const config = { ...defaultConfig };
40 |
41 | expect(getOptionsOverrides(config)).toStrictEqual(forcedOptions);
42 | });
43 |
44 | test("getOptionsOverrides - preParsedTsConfig", () => {
45 | const config = { ...defaultConfig };
46 | const preParsedTsConfig = { ...defaultPreParsedTsConfig };
47 |
48 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({
49 | ...forcedOptions,
50 | declarationDir: undefined,
51 | module: ts.ModuleKind.ES2015,
52 | sourceRoot: undefined,
53 | });
54 | });
55 |
56 | test("getOptionsOverrides - preParsedTsConfig with options.module", () => {
57 | const config = { ...defaultConfig };
58 | const preParsedTsConfig = {
59 | ...defaultPreParsedTsConfig,
60 | options: {
61 | module: ts.ModuleKind.AMD,
62 | },
63 | };
64 |
65 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({
66 | ...forcedOptions,
67 | declarationDir: undefined,
68 | sourceRoot: undefined,
69 | });
70 | });
71 |
72 | test("getOptionsOverrides - with declaration", () => {
73 | const config = { ...defaultConfig, useTsconfigDeclarationDir: true };
74 | const preParsedTsConfig = { ...defaultPreParsedTsConfig };
75 |
76 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({
77 | ...forcedOptions,
78 | module: ts.ModuleKind.ES2015,
79 | sourceRoot: undefined,
80 | });
81 | });
82 |
83 | test("getOptionsOverrides - with sourceMap", () => {
84 | const config = { ...defaultConfig };
85 | const preParsedTsConfig = {
86 | ...defaultPreParsedTsConfig,
87 | options: {
88 | sourceMap: true,
89 | },
90 | };
91 |
92 | expect(getOptionsOverrides(config, preParsedTsConfig)).toStrictEqual({
93 | ...forcedOptions,
94 | declarationDir: undefined,
95 | module: ts.ModuleKind.ES2015,
96 | });
97 | });
98 |
99 | test("createFilter", () => {
100 | const config = { ...defaultConfig };
101 | const preParsedTsConfig = { ...defaultPreParsedTsConfig };
102 | const filter = createFilter(makeContext(), config, preParsedTsConfig);
103 |
104 | expect(filter(filtPath("src/test.ts"))).toBe(true);
105 | expect(filter(filtPath("src/test.js"))).toBe(false);
106 | expect(filter(filtPath("src/test.d.ts"))).toBe(false);
107 | });
108 |
109 | test("createFilter - context.debug", () => {
110 | const config = { ...defaultConfig };
111 | const preParsedTsConfig = { ...defaultPreParsedTsConfig };
112 | const context = makeContext();
113 | createFilter(context, config, preParsedTsConfig);
114 |
115 | expect(context.debug).toHaveBeenCalledTimes(2);
116 | });
117 |
118 | test("createFilter - rootDirs", () => {
119 | const config = { ...defaultConfig };
120 | const preParsedTsConfig = {
121 | ...defaultPreParsedTsConfig,
122 | options: {
123 | rootDirs: ["src", "lib"]
124 | },
125 | };
126 | const filter = createFilter(makeContext(), config, preParsedTsConfig);
127 |
128 | expect(filter(filtPath("src/test.ts"))).toBe(true);
129 | expect(filter(filtPath("src/test.js"))).toBe(false);
130 | expect(filter(filtPath("src/test.d.ts"))).toBe(false);
131 |
132 | expect(filter(filtPath("lib/test.ts"))).toBe(true);
133 | expect(filter(filtPath("lib/test.js"))).toBe(false);
134 | expect(filter(filtPath("lib/test.d.ts"))).toBe(false);
135 |
136 | expect(filter(filtPath("not-src/test.ts"))).toBe(false);
137 | });
138 |
139 | test("createFilter - projectReferences", () => {
140 | // test string include and also don't match with "**"
141 | const config = { ...defaultConfig, include: "*.ts+(|x)" };
142 | const preParsedTsConfig = {
143 | ...defaultPreParsedTsConfig,
144 | projectReferences: [
145 | { path: "src" },
146 | { path: "lib" },
147 | ],
148 | };
149 | const filter = createFilter(makeContext(), config, preParsedTsConfig);
150 |
151 | expect(filter(filtPath("src/test.ts"))).toBe(true);
152 | expect(filter(filtPath("src/test.js"))).toBe(false);
153 | expect(filter(filtPath("src/test.d.ts"))).toBe(false);
154 |
155 | expect(filter(filtPath("lib/test.ts"))).toBe(true);
156 | expect(filter(filtPath("lib/test.js"))).toBe(false);
157 | expect(filter(filtPath("lib/test.d.ts"))).toBe(false);
158 |
159 | expect(filter(filtPath("not-src/test.ts"))).toBe(false);
160 | });
161 |
--------------------------------------------------------------------------------
/__tests__/host.spec.ts:
--------------------------------------------------------------------------------
1 | import { afterAll, beforeAll, test, expect, jest } from "@jest/globals";
2 | import * as ts from "typescript";
3 | import * as path from "path";
4 | import { normalizePath as normalize } from "@rollup/pluginutils";
5 | import { remove, ensureDir, writeFile, ensureSymlink } from "fs-extra";
6 |
7 | import { setTypescriptModule } from "../src/tsproxy";
8 | import { LanguageServiceHost } from "../src/host";
9 |
10 | setTypescriptModule(ts);
11 |
12 | // mock for host.trace
13 | (global as any).console = {
14 | log: jest.fn(),
15 | };
16 |
17 | const defaultConfig = { fileNames: [], errors: [], options: {} };
18 |
19 | const unaryFunc = "const unary = (x: string): string => x.reverse()";
20 | const unaryFuncSnap = { text: unaryFunc };
21 |
22 | // host.ts uses `/` normalized path, as does TS itself (https://github.com/microsoft/TypeScript/blob/7f022c58fb8b7253f23c49f0d9eee6fde82b477b/src/compiler/path.ts#L4)
23 | const local = (x: string) => normalize(path.resolve(__dirname, x));
24 | const testDir = local("__temp/host");
25 | const testFile = `${testDir}/file.ts`;
26 | const linkedTestFile = `${testDir}/link.ts`;
27 | const nonExistent = `${testDir}/this-does-not-exist.ts`;
28 |
29 | afterAll(() => remove(testDir));
30 | beforeAll(async () => {
31 | await ensureDir(testDir);
32 | await writeFile(testFile, unaryFunc, "utf8");
33 | await ensureSymlink(testFile, linkedTestFile);
34 | });
35 |
36 | test("LanguageServiceHost", async () => {
37 | const testOpts = { test: "this is a test" };
38 | const config = { ...defaultConfig, options: testOpts };
39 | const transformers = [() => ({})];
40 | const host = new LanguageServiceHost(config, transformers, testDir);
41 |
42 | // test core snapshot functionality
43 | expect(host.getScriptSnapshot(testFile)).toEqual(unaryFuncSnap);
44 | expect(host.getScriptVersion(testFile)).toEqual("1");
45 |
46 | expect(host.setSnapshot(testFile, unaryFunc)).toEqual(unaryFuncSnap); // version 2
47 | expect(host.getScriptSnapshot(testFile)).toEqual(unaryFuncSnap); // get from dict
48 | expect(host.getScriptVersion(testFile)).toEqual("2");
49 |
50 | expect(host.getScriptSnapshot(nonExistent)).toBeFalsy();
51 | expect(host.getScriptVersion(nonExistent)).toEqual("0");
52 |
53 | expect(host.getScriptFileNames()).toEqual([testFile]);
54 |
55 | host.reset(); // back to version 1
56 | expect(host.setSnapshot(testFile, unaryFunc)).toEqual(unaryFuncSnap);
57 | expect(host.getScriptVersion(testFile)).toEqual("1");
58 |
59 | // test fs functionality, which just uses tsModule.sys equivalents
60 | expect(host.getCurrentDirectory()).toEqual(testDir);
61 | expect(host.getDirectories(testDir)).toEqual([]);
62 | expect(host.directoryExists(nonExistent)).toBeFalsy();
63 | expect(host.fileExists(nonExistent)).toBeFalsy();
64 | expect(host.fileExists(testFile)).toBeTruthy();
65 | expect(host.readDirectory(testDir)).toEqual([testFile, linkedTestFile]);
66 | expect(host.readFile(nonExistent)).toBeFalsy();
67 | expect(host.readFile(testFile)).toEqual(unaryFunc);
68 | expect(host.useCaseSensitiveFileNames()).toBe(process.platform === "linux");
69 | // test realpath w/ symlinks. this returns a host path, so expect path.normalize()
70 | expect(host.realpath(testFile)).toEqual(path.normalize(testFile));
71 | expect(host.realpath(linkedTestFile)).toEqual(path.normalize(testFile));
72 |
73 | // test misc functionality
74 | expect(host.getCompilationSettings()).toEqual(testOpts);
75 | // TODO: check against `normalize(require.resolve("typescript/lib/lib.dts"))` once https://github.com/microsoft/TypeScript/issues/49050 is fixed -- endsWith is just a workaround for now
76 | expect(host.getDefaultLibFileName({}).endsWith("lib.d.ts")).toBeTruthy();
77 | expect(host.getTypeRootsVersion()).toEqual(0);
78 |
79 | // mock out trace
80 | host.trace('test log');
81 | expect(console.log).toHaveBeenCalledWith('test log');
82 | });
83 |
84 |
85 | test("LanguageServiceHost - getCustomTransformers", () => {
86 | const config = { ...defaultConfig };
87 | const transformers = [() => ({
88 | before: () => "testBefore",
89 | after: () => "testAfter",
90 | afterDeclarations: () => "testAfterDeclarations",
91 | })];
92 | const host = new LanguageServiceHost(config, transformers as any, testDir);
93 |
94 | host.setLanguageService(true as any);
95 | const customTransformers = host.getCustomTransformers();
96 | // tiny helper for all the type coercion etc
97 | const callTransform = (type: 'before' | 'after' | 'afterDeclarations') => {
98 | return customTransformers?.[type]?.[0](true as any);
99 | }
100 |
101 | expect(callTransform('before')).toEqual(transformers[0]().before());
102 | expect(callTransform('after')).toEqual(transformers[0]().after());
103 | expect(callTransform('afterDeclarations')).toEqual(transformers[0]().afterDeclarations());
104 | });
105 |
106 | test("LanguageServiceHost - getCustomTransformers -- undefined cases", () => {
107 | const config = { ...defaultConfig };
108 |
109 | // no LS and no transformers cases
110 | let host = new LanguageServiceHost(config, undefined as any, testDir);
111 | expect(host.getCustomTransformers()).toBeFalsy(); // no LS
112 | host.setLanguageService(true as any);
113 | expect(host.getCustomTransformers()).toBeFalsy(); // no transformers
114 |
115 | // empty transformers case
116 | host = new LanguageServiceHost(config, [], testDir);
117 | host.setLanguageService(true as any);
118 | expect(host.getCustomTransformers()).toBeFalsy(); // empty transformers
119 | });
120 |
--------------------------------------------------------------------------------
/__tests__/integration/errors.spec.ts:
--------------------------------------------------------------------------------
1 | import { jest, afterAll, test, expect } from "@jest/globals";
2 | import { Mock } from "jest-mock"
3 | import * as path from "path";
4 | import { normalizePath as normalize } from "@rollup/pluginutils";
5 | import * as fs from "fs-extra";
6 |
7 | import { RPT2Options } from "../../src/index";
8 | import { findName, genBundle as genBundleH } from "./helpers";
9 |
10 | // increase timeout to 15s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted
11 | jest.setTimeout(15000);
12 |
13 | const local = (x: string) => normalize(path.resolve(__dirname, x));
14 | const testDir = local("__temp/errors");
15 |
16 | afterAll(async () => {
17 | // workaround: there seems to be some race condition causing fs.remove to fail, so give it a sec first (c.f. https://github.com/jprichardson/node-fs-extra/issues/532)
18 | await new Promise(resolve => setTimeout(resolve, 1000));
19 | await fs.remove(testDir);
20 | });
21 |
22 | async function genBundle(relInput: string, extraOpts?: RPT2Options, onwarn?: Mock) {
23 | const input = local(`fixtures/errors/${relInput}`);
24 | return genBundleH({
25 | input,
26 | tsconfig: local("fixtures/errors/tsconfig.json"),
27 | testDir,
28 | extraOpts: { include: [input], ...extraOpts }, // only include the input itself, not other error files (to only generate types and type-check the one file)
29 | onwarn,
30 | });
31 | }
32 |
33 | test("integration - semantic error", async () => {
34 | await expect(genBundle("semantic.ts")).rejects.toThrow("Type 'string' is not assignable to type 'number'.");
35 | });
36 |
37 | test("integration - semantic error - abortOnError: false / check: false", async () => {
38 | const onwarn = jest.fn();
39 | // either warning or not type-checking should result in the same bundle
40 | const { output } = await genBundle("semantic.ts", { abortOnError: false }, onwarn);
41 | const { output: output2 } = await genBundle("semantic.ts", { check: false }, onwarn);
42 | expect(output).toEqual(output2);
43 |
44 | const files = ["index.js", "semantic.d.ts", "semantic.d.ts.map"];
45 | files.forEach(file => {
46 | expect(findName(output, file)).toBeTruthy();
47 | });
48 | expect(output.length).toEqual(files.length); // no other files
49 | expect(onwarn).toBeCalledTimes(1);
50 | });
51 |
52 | test("integration - syntax error", async () => {
53 | await expect(genBundle("syntax.ts")).rejects.toThrow("';' expected.");
54 | });
55 |
56 | test("integration - syntax error - abortOnError: false / check: false", async () => {
57 | const onwarn = jest.fn();
58 | const err = "Unexpected token (Note that you need plugins to import files that are not JavaScript)";
59 | await expect(genBundle("syntax.ts", { abortOnError: false }, onwarn)).rejects.toThrow(err);
60 | await expect(genBundle("syntax.ts", { check: false }, onwarn)).rejects.toThrow(err);
61 | });
62 |
63 | const typeOnlyIncludes = ["**/import-type-error.ts", "**/type-only-import-with-error.ts"];
64 |
65 | test("integration - type-only import error", async () => {
66 | await expect(genBundle("import-type-error.ts", {
67 | include: typeOnlyIncludes,
68 | })).rejects.toThrow("Property 'nonexistent' does not exist on type 'someObj'.");
69 | });
70 |
71 | test("integration - type-only import error - abortOnError: false / check: false", async () => {
72 | const onwarn = jest.fn();
73 | // either warning or not type-checking should result in the same bundle
74 | const { output } = await genBundle("import-type-error.ts", {
75 | include: typeOnlyIncludes,
76 | abortOnError: false,
77 | }, onwarn);
78 | const { output: output2 } = await genBundle("import-type-error.ts", {
79 | include: typeOnlyIncludes,
80 | check: false,
81 | }, onwarn);
82 | expect(output).toEqual(output2);
83 |
84 | const files = ["index.js", "import-type-error.d.ts", "import-type-error.d.ts.map", "type-only-import-with-error.d.ts.map", "type-only-import-with-error.d.ts.map"];
85 | files.forEach(file => {
86 | expect(findName(output, file)).toBeTruthy();
87 | });
88 | expect(output.length).toEqual(files.length); // no other files
89 | expect(onwarn).toBeCalledTimes(1);
90 | });
91 |
92 | // integration test variant of parse-tsconfig unit test, to test how other hooks interact with an error thrown in buildStart
93 | test("integration - tsconfig error", async () => {
94 | await expect(genBundle("semantic.ts", {
95 | tsconfigOverride: { compilerOptions: { module: "none" } },
96 | })).rejects.toThrow("Incompatible tsconfig option. Module resolves to 'None'. This is incompatible with Rollup, please use");
97 | });
98 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/errors/import-type-error.ts:
--------------------------------------------------------------------------------
1 | // this file has no errors itself; it is used an entry file to test an error in a type-only import
2 |
3 | export type { typeError } from "./type-only-import-with-error";
4 |
5 | // some code so this file isn't empty
6 | export function sum(a: number, b: number) {
7 | return a + b;
8 | }
9 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/errors/semantic.ts:
--------------------------------------------------------------------------------
1 | export const sum = (a: number, b: number): number => {
2 | return "a + b";
3 | }
4 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/errors/syntax.ts:
--------------------------------------------------------------------------------
1 | export const incorrectSyntax => {
2 | return "a + b";
3 | }
4 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/errors/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | }
4 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/errors/type-only-import-with-error.ts:
--------------------------------------------------------------------------------
1 | type someObj = {};
2 | export type typeError = someObj['nonexistent'];
3 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors.ts:
--------------------------------------------------------------------------------
1 | export const filesArr = [
2 | "index.js",
3 | "index.d.ts",
4 | "index.d.ts.map",
5 | "some-import.d.ts",
6 | "some-import.d.ts.map",
7 | "type-only-import.d.ts",
8 | "type-only-import.d.ts.map",
9 | "type-only-import-import.d.ts",
10 | "type-only-import-import.d.ts.map",
11 | ];
12 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors/index.ts:
--------------------------------------------------------------------------------
1 | export function sum(a: number, b: number) {
2 | return a + b;
3 | }
4 |
5 | import { difference } from "./some-import";
6 | export const diff2 = difference; // add an alias so that this file has to change when the import does (to help test cache invalidation etc)
7 |
8 | export { difference } from "./some-import"
9 | export type { num, num2 } from "./type-only-import"
10 |
11 | export { identity } from "./some-js-import"
12 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors/some-import.ts:
--------------------------------------------------------------------------------
1 | export function difference(a: number, b: number) {
2 | return a - b;
3 | }
4 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors/some-js-import.d.ts:
--------------------------------------------------------------------------------
1 | // TS needs a declaration in order to understand the import
2 | // but this is ambient, and so should not be directly imported into rpt2, just found by TS
3 |
4 | export function identity(a: any): any;
5 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors/some-js-import.js:
--------------------------------------------------------------------------------
1 | // should be filtered out by rpt2, but still bundled by Rollup itself (as this is ESM, no need for a plugin)
2 |
3 | export function identity(a) {
4 | return a;
5 | }
6 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | }
4 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors/type-only-import-import.ts:
--------------------------------------------------------------------------------
1 | export type numb = number;
2 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/no-errors/type-only-import.ts:
--------------------------------------------------------------------------------
1 | import type { numb } from "./type-only-import-import";
2 |
3 | export type num = number;
4 | export type num2 = numb;
5 |
--------------------------------------------------------------------------------
/__tests__/integration/fixtures/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "forceConsistentCasingInFileNames": true,
4 | "moduleResolution": "node",
5 | "sourceMap": true,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "target": "ES6",
9 | "strict": true,
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/__tests__/integration/helpers.ts:
--------------------------------------------------------------------------------
1 | import { rollup, watch, RollupOptions, OutputOptions, RollupOutput, OutputAsset, RollupWatcher } from "rollup";
2 | import * as path from "path";
3 |
4 | import rpt2, { RPT2Options } from "../../src/index";
5 |
6 | type Params = {
7 | input: string,
8 | tsconfig: string,
9 | testDir: string,
10 | extraOpts?: RPT2Options,
11 | onwarn?: RollupOptions['onwarn'],
12 | };
13 |
14 | function createInput ({ input, tsconfig, testDir, extraOpts, onwarn }: Params) {
15 | return {
16 | input,
17 | plugins: [rpt2({
18 | tsconfig,
19 | cacheRoot: `${testDir}/rpt2-cache`, // don't use the one in node_modules
20 | ...extraOpts,
21 | })],
22 | onwarn,
23 | }
24 | }
25 |
26 | function createOutput (testDir: string): OutputOptions {
27 | return {
28 | file: path.resolve(`${testDir}/dist/index.js`), // put outputs in temp test dir
29 | format: "esm",
30 | exports: "named",
31 | }
32 | }
33 |
34 | export async function genBundle (inputArgs: Params) {
35 | const bundle = await rollup(createInput(inputArgs));
36 | const esm = await bundle.generate(createOutput(inputArgs.testDir));
37 |
38 | // Rollup has some deprecated properties like `get isAsset`, so enumerating them with, e.g. `.toEqual`, causes a bunch of warnings to be output
39 | // delete the `isAsset` property for (much) cleaner logs
40 | const { output: files } = esm;
41 | for (const file of files) {
42 | if ("isAsset" in file) {
43 | const optIsAsset = file as Partial> & Omit;
44 | delete optIsAsset["isAsset"];
45 | }
46 | }
47 |
48 | return esm;
49 | }
50 |
51 | /** wrap Listener interface in a Promise */
52 | export function watchEnd (watcher: RollupWatcher) {
53 | return new Promise((resolve, reject) => {
54 | watcher.on("event", event => {
55 | if ("result" in event)
56 | event.result?.close(); // close all bundles
57 |
58 | if (event.code === "END")
59 | resolve();
60 | else if (event.code === "ERROR")
61 | reject(event.error);
62 | });
63 | });
64 | }
65 |
66 | export async function watchBundle (inputArgs: Params) {
67 | const watcher = watch({
68 | ...createInput(inputArgs),
69 | output: createOutput(inputArgs.testDir),
70 | });
71 |
72 | await watchEnd(watcher); // wait for build to end before returning, similar to genBundle
73 | return watcher;
74 | }
75 |
76 | export function findName (output: RollupOutput['output'], name: string) {
77 | // type-cast to simplify type-checking -- [0] is always chunk, rest are always asset in our case
78 | return output.find(file => file.fileName === name) as OutputAsset;
79 | }
80 |
--------------------------------------------------------------------------------
/__tests__/integration/no-errors.spec.ts:
--------------------------------------------------------------------------------
1 | import { jest, afterAll, test, expect } from "@jest/globals";
2 | import * as path from "path";
3 | import * as fs from "fs-extra";
4 | import { normalizePath as normalize } from "@rollup/pluginutils";
5 |
6 | import { RPT2Options } from "../../src/index";
7 | import { filesArr } from "./fixtures/no-errors";
8 | import { findName, genBundle as genBundleH } from "./helpers";
9 |
10 | // increase timeout to 20s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted
11 | jest.setTimeout(20000);
12 |
13 | const local = (x: string) => path.resolve(__dirname, x);
14 | const testDir = local("__temp/no-errors");
15 | const fixtureDir = local("fixtures/no-errors");
16 |
17 | afterAll(() => fs.remove(testDir));
18 |
19 | async function genBundle(relInput: string, extraOpts?: RPT2Options) {
20 | return genBundleH({
21 | input: `${fixtureDir}/${relInput}`,
22 | tsconfig: `${fixtureDir}/tsconfig.json`,
23 | testDir,
24 | extraOpts,
25 | });
26 | }
27 |
28 | test("integration - no errors", async () => {
29 | const { output } = await genBundle("index.ts", { clean: true });
30 |
31 | // populate the cache
32 | await genBundle("index.ts");
33 | const { output: outputWithCache } = await genBundle("index.ts");
34 | expect(output).toEqual(outputWithCache);
35 |
36 | const files = filesArr;
37 | files.forEach(file => {
38 | expect(findName(output, file)).toBeTruthy();
39 | });
40 | expect(output.length).toEqual(files.length); // no other files
41 |
42 | // JS file should be bundled by Rollup, even though rpt2 does not resolve it (as Rollup natively understands ESM)
43 | expect(output[0].code).toEqual(expect.stringContaining("identity"));
44 |
45 | // declaration map sources should be correctly remapped (and not point to placeholder dir, c.f. https://github.com/ezolenko/rollup-plugin-typescript2/pull/221)
46 | const decMap = findName(output, "index.d.ts.map");
47 | const decMapSources = JSON.parse(decMap.source as string).sources;
48 | const decRelPath = normalize(path.relative(`${testDir}/dist`, `${fixtureDir}/index.ts`));
49 | expect(decMapSources).toEqual([decRelPath]);
50 | });
51 |
52 | test("integration - no errors - using files list", async () => {
53 | const { output } = await genBundle("index.ts", { tsconfigOverride: { files: ["index.ts"] } });
54 |
55 | // should still have the type-only import and type-only import import!
56 | const files = filesArr;
57 | files.forEach(file => {
58 | expect(findName(output, file)).toBeTruthy();
59 | });
60 | expect(output.length).toEqual(files.length); // no other files
61 | });
62 |
63 | test("integration - no errors - no declaration maps", async () => {
64 | const noDeclarationMaps = { compilerOptions: { declarationMap: false } };
65 | const { output } = await genBundle("index.ts", {
66 | tsconfigOverride: noDeclarationMaps,
67 | clean: true,
68 | });
69 |
70 | const files = filesArr.filter(file => !file.endsWith(".d.ts.map"));
71 | files.forEach(file => {
72 | expect(findName(output, file)).toBeTruthy();
73 | });
74 | expect(output.length).toEqual(files.length); // no other files
75 | });
76 |
77 |
78 | test("integration - no errors - no declarations", async () => {
79 | const noDeclarations = { compilerOptions: { declaration: false, declarationMap: false } };
80 | const { output } = await genBundle("index.ts", {
81 | tsconfigOverride: noDeclarations,
82 | clean: true,
83 | });
84 |
85 | expect(output[0].fileName).toEqual("index.js");
86 | expect(output.length).toEqual(1); // no other files
87 | });
88 |
89 | test("integration - no errors - allowJs + emitDeclarationOnly", async () => {
90 | const { output } = await genBundle("some-js-import.js", {
91 | include: ["**/*.js"],
92 | tsconfigOverride: {
93 | compilerOptions: {
94 | allowJs: true,
95 | emitDeclarationOnly: true,
96 | },
97 | },
98 | });
99 |
100 | const files = ["index.js", "some-js-import.d.ts", "some-js-import.d.ts.map"];
101 | files.forEach(file => {
102 | expect(findName(output, file)).toBeTruthy();
103 | });
104 | expect(output.length).toEqual(files.length); // no other files
105 |
106 | expect(output[0].code).toEqual(expect.stringContaining("identity"));
107 | expect(output[0].code).not.toEqual(expect.stringContaining("sum")); // no TS files included
108 |
109 | const dec = findName(output, "some-js-import.d.ts");
110 | expect(dec.source).toEqual(expect.stringContaining("identity"));
111 | });
112 |
--------------------------------------------------------------------------------
/__tests__/integration/watch.spec.ts:
--------------------------------------------------------------------------------
1 | import { jest, beforeAll, afterAll, test, expect } from "@jest/globals";
2 | import * as path from "path";
3 | import * as fs from "fs-extra";
4 |
5 | import { RPT2Options } from "../../src/index";
6 | import { filesArr } from "./fixtures/no-errors";
7 | import * as helpers from "./helpers";
8 |
9 | // increase timeout to 15s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted
10 | jest.setTimeout(15000);
11 |
12 | const local = (x: string) => path.resolve(__dirname, x);
13 | const testDir = local("__temp/watch");
14 | const fixtureDir = `${testDir}/fixtures`;
15 |
16 | beforeAll(async () => {
17 | await fs.ensureDir(fixtureDir);
18 | // copy the dir to not interfere with other parallel tests since we need to change files for watch mode
19 | // note we're copying the root fixture dir bc we need the _base_ tsconfig too. maybe optimize in the future or use the other fixtures?
20 | await fs.copy(local("fixtures"), fixtureDir);
21 | });
22 | afterAll(() => fs.remove(testDir));
23 |
24 | async function watchBundle(input: string, extraOpts?: RPT2Options) {
25 | return helpers.watchBundle({
26 | input,
27 | tsconfig: `${path.dirname(input)}/tsconfig.json`, // use the tsconfig of whatever fixture we're in
28 | testDir,
29 | extraOpts,
30 | });
31 | }
32 |
33 | test("integration - watch", async () => {
34 | const srcPath = `${fixtureDir}/no-errors/index.ts`;
35 | const importPath = `${fixtureDir}/no-errors/some-import.ts`;
36 | const distDir = `${testDir}/dist`;
37 | const distPath = `${testDir}/dist/index.js`;
38 | const decPath = `${distDir}/index.d.ts`;
39 | const decMapPath = `${decPath}.map`;
40 |
41 | const watcher = await watchBundle(srcPath);
42 |
43 | const files = await fs.readdir(distDir);
44 | expect(files).toEqual(expect.arrayContaining(filesArr));
45 | expect(files.length).toBe(filesArr.length); // no other files
46 |
47 | // save content to test against later
48 | const dist = await fs.readFile(distPath, "utf8");
49 | const dec = await fs.readFile(decPath, "utf8");
50 | const decMap = await fs.readFile(decMapPath, "utf8");
51 |
52 | // modify an imported file -- this should cause it and index to change
53 | await fs.writeFile(importPath, "export const difference = 2", "utf8");
54 | await helpers.watchEnd(watcher);
55 |
56 | // should have same structure, since names haven't changed and dist hasn't been cleaned
57 | const files2 = await fs.readdir(distDir);
58 | expect(files2).toEqual(expect.arrayContaining(filesArr));
59 | expect(files2.length).toBe(filesArr.length); // no other files
60 |
61 | // should have different content now though
62 | expect(dist).not.toEqual(await fs.readFile(distPath, "utf8"));
63 | expect(dec).not.toEqual(await fs.readFile(decPath, "utf8"));
64 | expect(decMap).not.toEqual(await fs.readFile(decMapPath, "utf8"));
65 |
66 | // modify an imported file to cause a semantic error
67 | await fs.writeFile(importPath, "export const difference = nonexistent", "utf8")
68 | await expect(helpers.watchEnd(watcher)).rejects.toThrow("Cannot find name 'nonexistent'.");
69 |
70 | await watcher.close();
71 | });
72 |
--------------------------------------------------------------------------------
/__tests__/parse-tsconfig.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@jest/globals";
2 | import * as path from "path";
3 | import { normalizePath as normalize } from "@rollup/pluginutils";
4 |
5 | import { makeOptions } from "./fixtures/options";
6 | import { makeContext } from "./fixtures/context";
7 | import { parseTsConfig } from "../src/parse-tsconfig";
8 |
9 | const local = (x: string) => normalize(path.resolve(__dirname, x));
10 |
11 | const defaultOpts = makeOptions("", "");
12 |
13 | test("parseTsConfig", () => {
14 | const context = makeContext();
15 |
16 | parseTsConfig(context, defaultOpts);
17 |
18 | expect(context.error).not.toBeCalled();
19 | });
20 |
21 | test("parseTsConfig - incompatible module", () => {
22 | const context = makeContext();
23 |
24 | parseTsConfig(context, {
25 | ...defaultOpts,
26 | tsconfigOverride: { compilerOptions: { module: "none" } },
27 | });
28 |
29 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining("Incompatible tsconfig option. Module resolves to 'None'. This is incompatible with Rollup, please use"));
30 | });
31 |
32 | test("parseTsConfig - tsconfig errors", () => {
33 | const context = makeContext();
34 |
35 | parseTsConfig(context, {
36 | ...defaultOpts,
37 | tsconfigOverride: {
38 | include: "should-be-an-array",
39 | },
40 | });
41 |
42 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining("Compiler option 'include' requires a value of type Array"));
43 | });
44 |
45 | test("parseTsConfig - failed to open", () => {
46 | const context = makeContext();
47 | const nonExistentTsConfig = "non-existent-tsconfig";
48 |
49 | parseTsConfig(context, {
50 | ...defaultOpts,
51 | tsconfig: nonExistentTsConfig,
52 | })
53 |
54 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining(`failed to open '${nonExistentTsConfig}`));
55 | });
56 |
57 | test("parseTsConfig - failed to parse", () => {
58 | const context = makeContext();
59 | const notTsConfigPath = local("fixtures/options.ts"); // a TS file should fail to parse
60 |
61 | parseTsConfig(context, {
62 | ...defaultOpts,
63 | tsconfig: notTsConfigPath,
64 | })
65 |
66 | expect(context.error).toHaveBeenLastCalledWith(expect.stringContaining(`failed to parse '${notTsConfigPath}'`));
67 | });
68 |
--------------------------------------------------------------------------------
/__tests__/rollingcache.spec.ts:
--------------------------------------------------------------------------------
1 | import { beforeEach, afterAll, test, expect } from "@jest/globals";
2 | import * as path from "path";
3 | import { remove, ensureDir, writeFile, pathExists } from "fs-extra";
4 |
5 | import { RollingCache } from "../src/rollingcache";
6 |
7 |
8 | const local = (x: string) => path.resolve(__dirname, x);
9 | const testDir = local("__temp/rollingcache");
10 | const oldCacheDir = `${testDir}/cache`;
11 | const newCacheDir = `${testDir}/cache_`;
12 | const testFile = "file.json";
13 | const oldTestFile = `${oldCacheDir}/${testFile}`;
14 |
15 | const nonExistentFile = "this-does-not-exist.json";
16 | const emptyCacheRoot = `${testDir}/empty-cache-root`;
17 |
18 | const testFileShape = { a: 1, b: 2, c: 3 };
19 |
20 |
21 | beforeEach(async () => {
22 | await ensureDir(oldCacheDir);
23 | await ensureDir(newCacheDir);
24 | await writeFile(oldTestFile, JSON.stringify(testFileShape), "utf8");
25 | });
26 | afterAll(() => remove(testDir));
27 |
28 |
29 | test("RollingCache", async () => {
30 | const cache = new RollingCache(testDir);
31 |
32 | expect(cache.exists(nonExistentFile)).toBeFalsy();
33 | expect(cache.exists(testFile)).toBeTruthy();
34 | expect(cache.path("x")).toEqual(`${oldCacheDir}/x`);
35 | expect(cache.match([nonExistentFile])).toBeFalsy();
36 | expect(cache.match([testFile])).toBeTruthy();
37 | expect(cache.read(testFile)).toEqual(testFileShape);
38 |
39 | cache.write("write-test.json", {a: 2, b: 2, c: 2});
40 | expect(cache.read("write-test.json")).toEqual({a: 2, b: 2, c: 2});
41 |
42 | cache.write("write-fail.json", (undefined as any));
43 | expect(cache.read("write-fail.json")).toBeFalsy();
44 |
45 | cache.touch("touched.json");
46 | expect(await pathExists(`${newCacheDir}/touched.json`)).toBeTruthy();
47 |
48 | cache.roll();
49 | expect(await pathExists(newCacheDir)).toBeFalsy();
50 | });
51 |
52 | test("RollingCache, rolled", async () => {
53 | const cache = new RollingCache(testDir);
54 | // roll the cache
55 | cache.roll();
56 | // rolling again hits coverage for this.rolled being true already
57 | cache.roll();
58 | expect(cache.exists("anything")).toBeFalsy();
59 | expect(cache.match([])).toBeFalsy();
60 |
61 | cache.write("whatever.json", {whatever: true});
62 | expect(await pathExists(`${oldCacheDir}/whatever.json`)).toBeFalsy();
63 |
64 | cache.touch("touched.json");
65 | expect(await pathExists(`${oldCacheDir}/touched.json`)).toBeFalsy();
66 | });
67 |
68 | test("RollingCache, test newCache", async () => {
69 | const cache = new RollingCache(testDir);
70 |
71 | const preExistingFile = `${newCacheDir}/pre-existing.json`;
72 | await writeFile(preExistingFile, JSON.stringify({}));
73 | expect(cache.exists("pre-existing.json")).toBeTruthy();
74 | });
75 |
76 | test("RollingCache, test match when oldCacheDir is empty", () => {
77 | const cache = new RollingCache(emptyCacheRoot);
78 |
79 | expect(cache.match([])).toBeTruthy();
80 | expect(cache.match([testFile])).toBeFalsy();
81 | });
82 |
--------------------------------------------------------------------------------
/__tests__/tslib.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect, jest } from "@jest/globals";
2 | import * as fs from "fs-extra";
3 |
4 | (global as any).console = { warn: jest.fn() };
5 |
6 | // the error case _must_ come first because order matters for module mocks
7 | test("tslib - errors", async () => {
8 | jest.mock("tslib/package.json", () => undefined); // mock the module subpath bc we actually never import "tslib" directly. module mocks only work on exact match
9 | await expect(import("../src/tslib")).rejects.toThrow();
10 | expect(console.warn).toBeCalledTimes(1);
11 | });
12 |
13 | test("tslib", async () => {
14 | jest.unmock("tslib/package.json");
15 |
16 | const { tslibVersion, tslibSource } = await import("../src/tslib");
17 | // eslint-disable-next-line @typescript-eslint/no-var-requires
18 | expect(tslibVersion).toEqual(require("tslib/package.json").version);
19 |
20 | const tslibES6 = await fs.readFile(require.resolve("tslib/tslib.es6.js"), "utf8");
21 | expect(tslibSource).toEqual(tslibES6);
22 | });
23 |
--------------------------------------------------------------------------------
/dist/context.d.ts:
--------------------------------------------------------------------------------
1 | import { PluginContext } from "rollup";
2 | export declare enum VerbosityLevel {
3 | Error = 0,
4 | Warning = 1,
5 | Info = 2,
6 | Debug = 3
7 | }
8 | /** cannot be used in options hook (which does not have this.warn and this.error), but can be in other hooks */
9 | export declare class RollupContext {
10 | private verbosity;
11 | private bail;
12 | private context;
13 | private prefix;
14 | constructor(verbosity: VerbosityLevel, bail: boolean, context: PluginContext, prefix?: string);
15 | warn(message: string | (() => string)): void;
16 | error(message: string | (() => string)): void | never;
17 | info(message: string | (() => string)): void;
18 | debug(message: string | (() => string)): void;
19 | }
20 | //# sourceMappingURL=context.d.ts.map
--------------------------------------------------------------------------------
/dist/context.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,oBAAY,cAAc;IAEzB,KAAK,IAAI;IACT,OAAO,IAAA;IACP,IAAI,IAAA;IACJ,KAAK,IAAA;CACL;AAMD,+GAA+G;AAC/G,qBAAa,aAAa;IAEb,OAAO,CAAC,SAAS;IAAkB,OAAO,CAAC,IAAI;IAAW,OAAO,CAAC,OAAO;IAAiB,OAAO,CAAC,MAAM;gBAAhG,SAAS,EAAE,cAAc,EAAU,IAAI,EAAE,OAAO,EAAU,OAAO,EAAE,aAAa,EAAU,MAAM,GAAE,MAAW;IAI1H,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI;IAO5C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI,GAAG,KAAK;IAWrD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI;IAO5C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI;CAMpD"}
--------------------------------------------------------------------------------
/dist/diagnostics-format-host.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import * as path from "path";
3 | import * as tsTypes from "typescript";
4 | export declare class FormatHost implements tsTypes.FormatDiagnosticsHost {
5 | getCurrentDirectory(): string;
6 | getCanonicalFileName: typeof path.normalize;
7 | getNewLine: () => string;
8 | }
9 | export declare const formatHost: FormatHost;
10 | //# sourceMappingURL=diagnostics-format-host.d.ts.map
--------------------------------------------------------------------------------
/dist/diagnostics-format-host.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"diagnostics-format-host.d.ts","sourceRoot":"","sources":["../src/diagnostics-format-host.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,qBAAa,UAAW,YAAW,OAAO,CAAC,qBAAqB;IAExD,mBAAmB,IAAI,MAAM;IAK7B,oBAAoB,wBAAkB;IACtC,UAAU,eAA8B;CAC/C;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
--------------------------------------------------------------------------------
/dist/diagnostics.d.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import { RollupContext } from "./context";
3 | export interface IDiagnostics {
4 | flatMessage: string;
5 | formatted: string;
6 | fileLine?: string;
7 | category: tsTypes.DiagnosticCategory;
8 | code: number;
9 | type: string;
10 | }
11 | export declare function convertDiagnostic(type: string, data: tsTypes.Diagnostic[]): IDiagnostics[];
12 | export declare function printDiagnostics(context: RollupContext, diagnostics: IDiagnostics[], pretty?: boolean): void;
13 | //# sourceMappingURL=diagnostics.d.ts.map
--------------------------------------------------------------------------------
/dist/diagnostics.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,MAAM,WAAW,YAAY;IAE5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,GAAG,YAAY,EAAE,CAoB1F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,MAAM,UAAO,GAAG,IAAI,CAqCzG"}
--------------------------------------------------------------------------------
/dist/get-options-overrides.d.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import { IOptions } from "./ioptions";
3 | import { RollupContext } from "./context";
4 | export declare function getOptionsOverrides({ useTsconfigDeclarationDir, cacheRoot }: IOptions, preParsedTsconfig?: tsTypes.ParsedCommandLine): tsTypes.CompilerOptions;
5 | export declare function createFilter(context: RollupContext, pluginOptions: IOptions, parsedConfig: tsTypes.ParsedCommandLine): (id: unknown) => boolean;
6 | //# sourceMappingURL=get-options-overrides.d.ts.map
--------------------------------------------------------------------------------
/dist/get-options-overrides.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"get-options-overrides.d.ts","sourceRoot":"","sources":["../src/get-options-overrides.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,wBAAgB,mBAAmB,CAAC,EAAE,yBAAyB,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,iBAAiB,GAAG,OAAO,CAAC,eAAe,CA+B9J;AAeD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,iBAAiB,4BAoBpH"}
--------------------------------------------------------------------------------
/dist/host.d.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import { TransformerFactoryCreator } from "./ioptions";
3 | export declare class LanguageServiceHost implements tsTypes.LanguageServiceHost {
4 | private parsedConfig;
5 | private transformers;
6 | private cwd;
7 | private snapshots;
8 | private versions;
9 | private service?;
10 | private fileNames;
11 | constructor(parsedConfig: tsTypes.ParsedCommandLine, transformers: TransformerFactoryCreator[], cwd: string);
12 | reset(): void;
13 | setLanguageService(service: tsTypes.LanguageService): void;
14 | setSnapshot(fileName: string, source: string): tsTypes.IScriptSnapshot;
15 | getScriptSnapshot(fileName: string): tsTypes.IScriptSnapshot | undefined;
16 | getScriptFileNames: () => string[];
17 | getScriptVersion(fileName: string): string;
18 | getCustomTransformers(): tsTypes.CustomTransformers | undefined;
19 | getCompilationSettings: () => tsTypes.CompilerOptions;
20 | getTypeRootsVersion: () => number;
21 | getCurrentDirectory: () => string;
22 | useCaseSensitiveFileNames: () => boolean;
23 | getDefaultLibFileName: typeof tsTypes.getDefaultLibFilePath;
24 | readDirectory: (path: string, extensions?: readonly string[] | undefined, exclude?: readonly string[] | undefined, include?: readonly string[] | undefined, depth?: number | undefined) => string[];
25 | readFile: (path: string, encoding?: string | undefined) => string | undefined;
26 | fileExists: (path: string) => boolean;
27 | directoryExists: (path: string) => boolean;
28 | getDirectories: (path: string) => string[];
29 | realpath: (path: string) => string;
30 | trace: {
31 | (...data: any[]): void;
32 | (message?: any, ...optionalParams: any[]): void;
33 | };
34 | }
35 | //# sourceMappingURL=host.d.ts.map
--------------------------------------------------------------------------------
/dist/host.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAItC,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD,qBAAa,mBAAoB,YAAW,OAAO,CAAC,mBAAmB;IAO1D,OAAO,CAAC,YAAY;IAA6B,OAAO,CAAC,YAAY;IAA+B,OAAO,CAAC,GAAG;IAL3H,OAAO,CAAC,SAAS,CAAuD;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,OAAO,CAAC,CAA0B;IAC1C,OAAO,CAAC,SAAS,CAAc;gBAEX,YAAY,EAAE,OAAO,CAAC,iBAAiB,EAAU,YAAY,EAAE,yBAAyB,EAAE,EAAU,GAAG,EAAE,MAAM;IAK5H,KAAK;IAML,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe;IAKnD,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe;IAWtE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS;IAcxE,kBAAkB,iBAA6C;IAE/D,gBAAgB,CAAC,QAAQ,EAAE,MAAM;IAOjC,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS;IA0B/D,sBAAsB,gCAAmC;IACzD,mBAAmB,eAAW;IAC9B,mBAAmB,eAAkB;IAErC,yBAAyB,gBAAgD;IACzE,qBAAqB,uCAAkC;IAEvD,aAAa,uLAA8B;IAC3C,QAAQ,sEAAyB;IACjC,UAAU,4BAA2B;IACrC,eAAe,4BAAgC;IAC/C,cAAc,6BAA+B;IAC7C,QAAQ,2BAA0B;IAElC,KAAK;;;MAAe;CAC3B"}
--------------------------------------------------------------------------------
/dist/icache.d.ts:
--------------------------------------------------------------------------------
1 | export interface ICache {
2 | exists(name: string): boolean;
3 | path(name: string): string;
4 | match(names: string[]): boolean;
5 | read(name: string): DataType | null | undefined;
6 | write(name: string, data: DataType): void;
7 | touch(name: string): void;
8 | roll(): void;
9 | }
10 | //# sourceMappingURL=icache.d.ts.map
--------------------------------------------------------------------------------
/dist/icache.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"icache.d.ts","sourceRoot":"","sources":["../src/icache.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM,CAAE,QAAQ;IAEhC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAE9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEhC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC;IAEhD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAE1C,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,IAAI,IAAI,IAAI,CAAC;CACb"}
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import { PluginImpl } from "rollup";
2 | import { IOptions } from "./ioptions";
3 | type RPT2Options = Partial;
4 | export { RPT2Options };
5 | declare const typescript: PluginImpl;
6 | export default typescript;
7 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/dist/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAoD,MAAM,QAAQ,CAAC;AAUtF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAWtC,KAAK,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAErC,OAAO,EAAE,WAAW,EAAE,CAAA;AAEtB,QAAA,MAAM,UAAU,EAAE,UAAU,CAAC,WAAW,CA6ZvC,CAAC;AAEF,eAAe,UAAU,CAAC"}
--------------------------------------------------------------------------------
/dist/ioptions.d.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import { tsModule } from "./tsproxy";
3 | export interface ICustomTransformer {
4 | before?: tsTypes.TransformerFactory;
5 | after?: tsTypes.TransformerFactory;
6 | afterDeclarations?: tsTypes.TransformerFactory;
7 | }
8 | export type TransformerFactoryCreator = (ls: tsTypes.LanguageService) => tsTypes.CustomTransformers | ICustomTransformer;
9 | export interface IOptions {
10 | cwd: string;
11 | include: string | string[];
12 | exclude: string | string[];
13 | check: boolean;
14 | verbosity: number;
15 | clean: boolean;
16 | cacheRoot: string;
17 | abortOnError: boolean;
18 | rollupCommonJSResolveHack: boolean;
19 | tsconfig?: string;
20 | useTsconfigDeclarationDir: boolean;
21 | typescript: typeof tsModule;
22 | tsconfigOverride: any;
23 | transformers: TransformerFactoryCreator[];
24 | tsconfigDefaults: any;
25 | sourceMapCallback: (id: string, map: string) => void;
26 | objectHashIgnoreUnknownHack: boolean;
27 | }
28 | //# sourceMappingURL=ioptions.d.ts.map
--------------------------------------------------------------------------------
/dist/ioptions.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"ioptions.d.ts","sourceRoot":"","sources":["../src/ioptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,WAAW,kBAAkB;IAElC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,KAAK,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpF;AAED,MAAM,MAAM,yBAAyB,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,eAAe,KAAK,OAAO,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;AAEzH,MAAM,WAAW,QAAQ;IAExB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAC,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,GAAC,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,yBAAyB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yBAAyB,EAAE,OAAO,CAAC;IACnC,UAAU,EAAE,OAAO,QAAQ,CAAC;IAC5B,gBAAgB,EAAE,GAAG,CAAC;IACtB,YAAY,EAAE,yBAAyB,EAAE,CAAC;IAC1C,gBAAgB,EAAE,GAAG,CAAC;IACtB,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,2BAA2B,EAAE,OAAO,CAAC;CACrC"}
--------------------------------------------------------------------------------
/dist/parse-tsconfig.d.ts:
--------------------------------------------------------------------------------
1 | import { RollupContext } from "./context";
2 | import { IOptions } from "./ioptions";
3 | export declare function parseTsConfig(context: RollupContext, pluginOptions: IOptions): {
4 | parsedTsConfig: import("typescript").ParsedCommandLine;
5 | fileName: string | undefined;
6 | };
7 | //# sourceMappingURL=parse-tsconfig.d.ts.map
--------------------------------------------------------------------------------
/dist/parse-tsconfig.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"parse-tsconfig.d.ts","sourceRoot":"","sources":["../src/parse-tsconfig.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ;;;EA8C5E"}
--------------------------------------------------------------------------------
/dist/rollingcache.d.ts:
--------------------------------------------------------------------------------
1 | import { ICache } from "./icache";
2 | /**
3 | * Saves data in new cache folder or reads it from old one.
4 | * Avoids perpetually growing cache and situations when things need to consider changed and then reverted data to be changed.
5 | */
6 | export declare class RollingCache implements ICache {
7 | private cacheRoot;
8 | private oldCacheRoot;
9 | private newCacheRoot;
10 | private rolled;
11 | /** @param cacheRoot: root folder for the cache */
12 | constructor(cacheRoot: string);
13 | /** @returns true if name exists in either old cache or new cache */
14 | exists(name: string): boolean;
15 | path(name: string): string;
16 | /** @returns true if old cache contains all names and nothing more */
17 | match(names: string[]): boolean;
18 | /** @returns data for name, must exist in either old cache or new cache */
19 | read(name: string): DataType | null | undefined;
20 | write(name: string, data: DataType): void;
21 | touch(name: string): void;
22 | /** clears old cache and moves new in its place */
23 | roll(): void;
24 | }
25 | //# sourceMappingURL=rollingcache.d.ts.map
--------------------------------------------------------------------------------
/dist/rollingcache.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"rollingcache.d.ts","sourceRoot":"","sources":["../src/rollingcache.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;;GAGG;AACH,qBAAa,YAAY,CAAC,QAAQ,CAAE,YAAW,MAAM,CAAC,QAAQ,CAAC;IAOlD,OAAO,CAAC,SAAS;IAL7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAkB;IAEhC,kDAAkD;gBAC9B,SAAS,EAAE,MAAM;IAOrC,oEAAoE;IAC7D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAW7B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAKjC,qEAAqE;IAC9D,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAWtC,0EAA0E;IACnE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS;IAQ/C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAWzC,KAAK,CAAC,IAAI,EAAE,MAAM;IAQzB,kDAAkD;IAC3C,IAAI;CAWX"}
--------------------------------------------------------------------------------
/dist/tscache.d.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import { RollupContext } from "./context";
3 | import { IDiagnostics } from "./diagnostics";
4 | export interface ICode {
5 | code: string;
6 | map?: string;
7 | dts?: tsTypes.OutputFile;
8 | dtsmap?: tsTypes.OutputFile;
9 | references?: string[];
10 | }
11 | export declare function convertEmitOutput(output: tsTypes.EmitOutput, references?: string[]): ICode;
12 | export declare function getAllReferences(importer: string, snapshot: tsTypes.IScriptSnapshot | undefined, options: tsTypes.CompilerOptions): string[];
13 | export declare class TsCache {
14 | private noCache;
15 | private host;
16 | private cacheRoot;
17 | private options;
18 | private rollupConfig;
19 | private context;
20 | private cacheVersion;
21 | private cachePrefix;
22 | private dependencyTree;
23 | private ambientTypes;
24 | private ambientTypesDirty;
25 | private cacheDir;
26 | private codeCache;
27 | private typesCache;
28 | private semanticDiagnosticsCache;
29 | private syntacticDiagnosticsCache;
30 | private hashOptions;
31 | constructor(noCache: boolean, runClean: boolean, hashIgnoreUnknown: boolean, host: tsTypes.LanguageServiceHost, cacheRoot: string, options: tsTypes.CompilerOptions, rollupConfig: any, rootFilenames: string[], context: RollupContext);
32 | private clean;
33 | setDependency(importee: string, importer: string): void;
34 | walkTree(cb: (id: string) => void | false): void;
35 | done(): void;
36 | getCompiled(id: string, snapshot: tsTypes.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined;
37 | getSyntacticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[];
38 | getSemanticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[];
39 | private checkAmbientTypes;
40 | private getDiagnostics;
41 | private getCached;
42 | private init;
43 | private markAsDirty;
44 | /** @returns true if node, any of its imports, or any ambient types changed */
45 | private isDirty;
46 | /** @returns an FS-safe hash string for use as a path to the cached content */
47 | private createHash;
48 | }
49 | //# sourceMappingURL=tscache.d.ts.map
--------------------------------------------------------------------------------
/dist/tscache.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"tscache.d.ts","sourceRoot":"","sources":["../src/tscache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAOtC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI1C,OAAO,EAAE,YAAY,EAAqB,MAAM,eAAe,CAAC;AAEhE,MAAM,WAAW,KAAK;IAErB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAaD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAiB1F;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,YAYjI;AAED,qBAAa,OAAO;IAcP,OAAO,CAAC,OAAO;IAA0D,OAAO,CAAC,IAAI;IAA+B,OAAO,CAAC,SAAS;IAAU,OAAO,CAAC,OAAO;IAA2B,OAAO,CAAC,YAAY;IAAgC,OAAO,CAAC,OAAO;IAZxQ,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,WAAW,CAAW;IAC9B,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,wBAAwB,CAA0B;IAC1D,OAAO,CAAC,yBAAyB,CAA0B;IAC3D,OAAO,CAAC,WAAW,CAA+C;gBAE9C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAU,IAAI,EAAE,OAAO,CAAC,mBAAmB,EAAU,SAAS,EAAE,MAAM,EAAU,OAAO,EAAE,OAAO,CAAC,eAAe,EAAU,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,EAAU,OAAO,EAAE,aAAa;IAqCvR,OAAO,CAAC,KAAK;IA6BN,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQvD,QAAQ,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,KAAK,GAAG,IAAI;IAShD,IAAI;IAYJ,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,MAAM,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS;IAOjH,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,GAAG,YAAY,EAAE;IAKzH,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,GAAG,YAAY,EAAE;IAK/H,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,SAAS;IA8BjB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,WAAW;IAKnB,8EAA8E;IAC9E,OAAO,CAAC,OAAO;IA+Bf,8EAA8E;IAC9E,OAAO,CAAC,UAAU;CAKlB"}
--------------------------------------------------------------------------------
/dist/tslib.d.ts:
--------------------------------------------------------------------------------
1 | export declare const TSLIB = "tslib";
2 | export declare const TSLIB_VIRTUAL = "\0tslib.js";
3 | export declare let tslibSource: string;
4 | export declare let tslibVersion: string;
5 | //# sourceMappingURL=tslib.d.ts.map
--------------------------------------------------------------------------------
/dist/tslib.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"tslib.d.ts","sourceRoot":"","sources":["../src/tslib.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,KAAK,UAAU,CAAC;AAC7B,eAAO,MAAM,aAAa,eAAe,CAAC;AAC1C,eAAO,IAAI,WAAW,EAAE,MAAM,CAAC;AAC/B,eAAO,IAAI,YAAY,EAAE,MAAM,CAAC"}
--------------------------------------------------------------------------------
/dist/tsproxy.d.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | export declare let tsModule: typeof tsTypes;
3 | export declare function setTypescriptModule(override: typeof tsTypes): void;
4 | //# sourceMappingURL=tsproxy.d.ts.map
--------------------------------------------------------------------------------
/dist/tsproxy.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"tsproxy.d.ts","sourceRoot":"","sources":["../src/tsproxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAEtC,eAAO,IAAI,QAAQ,EAAE,OAAO,OAAO,CAAC;AAEpC,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,OAAO,QAG3D"}
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const pkg = require("./package.json");
2 |
3 | /** @type {import("ts-jest").InitialOptionsTsJest} */
4 | const config = {
5 | // ts-jest settings
6 | preset: "ts-jest",
7 | globals: {
8 | "ts-jest": {
9 | tsconfig: "./tsconfig.test.json",
10 | },
11 | // other globals (unrelated to ts-jest) -- these are namespaced so they don't conflict with anything else
12 | "rpt2__TS_VERSION_RANGE": pkg.peerDependencies.typescript,
13 | "rpt2__ROLLUP_VERSION_RANGE": pkg.peerDependencies.rollup,
14 | "rpt2__RPT2_VERSION": pkg.version,
15 | },
16 |
17 | // jest settings
18 | injectGlobals: false, // use @jest/globals instead
19 | restoreMocks: true,
20 | // only use *.spec.ts files in __tests__, no auto-generated files
21 | testMatch: ["**/__tests__/**/*.spec.ts?(x)"],
22 | coveragePathIgnorePatterns: [
23 | "node_modules", // default
24 | "/__tests__/" // ignore any test helper files
25 | ],
26 | };
27 |
28 | module.exports = config;
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rollup-plugin-typescript2",
3 | "version": "0.36.1",
4 | "description": "Seamless integration between Rollup and TypeScript. Now with errors.",
5 | "main": "dist/rollup-plugin-typescript2.cjs.js",
6 | "module": "dist/rollup-plugin-typescript2.es.js",
7 | "jsnext:main": "dist/rollup-plugin-typescript2.es.js",
8 | "types": "dist/index.d.ts",
9 | "files": [
10 | "dist"
11 | ],
12 | "keywords": [
13 | "rollup-plugin-typescript2",
14 | "rollup-plugin-typescript",
15 | "rollup-plugin",
16 | "typescript",
17 | "es2015",
18 | "rollup",
19 | "npm"
20 | ],
21 | "license": "MIT",
22 | "homepage": "https://github.com/ezolenko/rollup-plugin-typescript2",
23 | "author": "@ezolenko",
24 | "scripts": {
25 | "prebuild": "rimraf dist/*",
26 | "build": "rimraf dist/* && rollup -c",
27 | "watch": "rollup -c rollup.config.self.js -w ",
28 | "build-self": "rimraf dist/* && rollup -c rollup.config.self.js",
29 | "lint": "eslint .",
30 | "test": "jest",
31 | "test:watch": "jest --watch",
32 | "test:coverage": "jest --coverage"
33 | },
34 | "dependencies": {
35 | "@rollup/pluginutils": "^4.1.2",
36 | "find-cache-dir": "^3.3.2",
37 | "fs-extra": "^10.0.0",
38 | "semver": "^7.5.4",
39 | "tslib": "^2.6.2"
40 | },
41 | "peerDependencies": {
42 | "rollup": ">=1.26.3",
43 | "typescript": ">=2.4.0"
44 | },
45 | "devDependencies": {
46 | "@jest/globals": "^28.0.3",
47 | "@rollup/plugin-commonjs": "^22.0.0",
48 | "@rollup/plugin-node-resolve": "13.2.1",
49 | "@types/find-cache-dir": "^2.0.0",
50 | "@types/fs-extra": "8.0.1",
51 | "@types/graphlib": "2.1.7",
52 | "@types/jest": "^27.5.0",
53 | "@types/lodash": "4.14.161",
54 | "@types/node": "8.0.47",
55 | "@types/object-hash": "1.3.3",
56 | "@types/semver": "7.3.12",
57 | "@typescript-eslint/eslint-plugin": "^6.7.3",
58 | "@typescript-eslint/parser": "^6.7.3",
59 | "colors": "1.4.0",
60 | "eslint": "^8.50.0",
61 | "graphlib": "2.1.8",
62 | "jest": "^29.7.0",
63 | "lodash": "4.17.21",
64 | "object-hash": "3.0.0",
65 | "rimraf": "3.0.2",
66 | "rollup": "^2.70.2",
67 | "rollup-plugin-re": "1.0.7",
68 | "rollup-plugin-typescript2": "0.35.0",
69 | "ts-jest": "^29.1.1",
70 | "typescript": "^5.1.3"
71 | },
72 | "repository": {
73 | "type": "git",
74 | "url": "git+https://github.com/ezolenko/rollup-plugin-typescript2.git"
75 | },
76 | "bugs": {
77 | "url": "https://github.com/ezolenko/rollup-plugin-typescript2/issues"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/rollup.config.base.js:
--------------------------------------------------------------------------------
1 | import resolve from "@rollup/plugin-node-resolve";
2 | import commonjs from "@rollup/plugin-commonjs";
3 | import replace from "rollup-plugin-re";
4 |
5 | const pkg = require("./package.json");
6 |
7 | export default {
8 | input: "src/index.ts",
9 |
10 | external: [
11 | // Node built-ins
12 | "fs",
13 | "crypto",
14 | "path",
15 | "constants",
16 | "stream",
17 | "util",
18 | "assert",
19 | "os",
20 | // deps
21 | "fs-extra",
22 | "semver",
23 | "@rollup/pluginutils",
24 | ],
25 |
26 | plugins: [
27 | replace
28 | ({
29 | replaces:
30 | {
31 | "$TS_VERSION_RANGE": pkg.peerDependencies.typescript,
32 | "$ROLLUP_VERSION_RANGE": pkg.peerDependencies.rollup,
33 | "$RPT2_VERSION": pkg.version,
34 | },
35 | }),
36 | resolve({ jsnext: true, preferBuiltins: true, }),
37 | commonjs
38 | ({
39 | include: "node_modules/**",
40 | }),
41 | ],
42 |
43 | output: [
44 | {
45 | format: "cjs",
46 | file: pkg.main,
47 | sourcemap: true,
48 | banner: "/* eslint-disable */",
49 | exports: "auto",
50 | },
51 | {
52 | format: "es",
53 | file: pkg.module,
54 | sourcemap: true,
55 | banner: "/* eslint-disable */",
56 | exports: "auto",
57 | },
58 | {
59 | format: "es",
60 | file: "build-self/" + pkg.module,
61 | sourcemap: true,
62 | banner: "/* eslint-disable */",
63 | exports: "auto",
64 | },
65 | ],
66 | };
67 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import ts from "rollup-plugin-typescript2";
2 |
3 | import config from "./rollup.config.base";
4 |
5 | config.plugins.push(ts({ verbosity: 2, abortOnError: false }));
6 |
7 | export default config;
8 |
--------------------------------------------------------------------------------
/rollup.config.self.js:
--------------------------------------------------------------------------------
1 | import ts from "./build-self/dist/rollup-plugin-typescript2.es";
2 |
3 | import config from "./rollup.config.base";
4 |
5 | config.plugins.push(ts({ verbosity: 2, abortOnError: false, clean: false }));
6 |
7 | export default config;
8 |
--------------------------------------------------------------------------------
/src/context.ts:
--------------------------------------------------------------------------------
1 | import { PluginContext } from "rollup";
2 |
3 | export enum VerbosityLevel
4 | {
5 | Error = 0,
6 | Warning,
7 | Info,
8 | Debug,
9 | }
10 |
11 | function getText (message: string | (() => string)): string {
12 | return typeof message === "string" ? message : message();
13 | }
14 |
15 | /** cannot be used in options hook (which does not have this.warn and this.error), but can be in other hooks */
16 | export class RollupContext
17 | {
18 | constructor(private verbosity: VerbosityLevel, private bail: boolean, private context: PluginContext, private prefix: string = "")
19 | {
20 | }
21 |
22 | public warn(message: string | (() => string)): void
23 | {
24 | if (this.verbosity < VerbosityLevel.Warning)
25 | return;
26 | this.context.warn(`${getText(message)}`);
27 | }
28 |
29 | public error(message: string | (() => string)): void | never
30 | {
31 | if (this.verbosity < VerbosityLevel.Error)
32 | return;
33 |
34 | if (this.bail)
35 | this.context.error(`${getText(message)}`);
36 | else
37 | this.context.warn(`${getText(message)}`);
38 | }
39 |
40 | public info(message: string | (() => string)): void
41 | {
42 | if (this.verbosity < VerbosityLevel.Info)
43 | return;
44 | console.log(`${this.prefix}${getText(message)}`);
45 | }
46 |
47 | public debug(message: string | (() => string)): void
48 | {
49 | if (this.verbosity < VerbosityLevel.Debug)
50 | return;
51 | console.log(`${this.prefix}${getText(message)}`);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/diagnostics-format-host.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | import * as tsTypes from "typescript";
3 |
4 | import { tsModule } from "./tsproxy";
5 |
6 | export class FormatHost implements tsTypes.FormatDiagnosticsHost
7 | {
8 | public getCurrentDirectory(): string
9 | {
10 | return tsModule.sys.getCurrentDirectory();
11 | }
12 |
13 | public getCanonicalFileName = path.normalize;
14 | public getNewLine = () => tsModule.sys.newLine;
15 | }
16 |
17 | export const formatHost = new FormatHost();
18 |
--------------------------------------------------------------------------------
/src/diagnostics.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import { red, white, yellow } from "colors/safe";
3 |
4 | import { tsModule } from "./tsproxy";
5 | import { RollupContext } from "./context";
6 | import { formatHost } from "./diagnostics-format-host";
7 |
8 | export interface IDiagnostics
9 | {
10 | flatMessage: string;
11 | formatted: string;
12 | fileLine?: string;
13 | category: tsTypes.DiagnosticCategory;
14 | code: number;
15 | type: string;
16 | }
17 |
18 | export function convertDiagnostic(type: string, data: tsTypes.Diagnostic[]): IDiagnostics[]
19 | {
20 | return data.map((diagnostic) =>
21 | {
22 | const entry: IDiagnostics = {
23 | flatMessage: tsModule.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()),
24 | formatted: tsModule.formatDiagnosticsWithColorAndContext(data, formatHost),
25 | category: diagnostic.category,
26 | code: diagnostic.code,
27 | type,
28 | };
29 |
30 | if (diagnostic.file && diagnostic.start !== undefined)
31 | {
32 | const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
33 | entry.fileLine = `${diagnostic.file.fileName}(${line + 1},${character + 1})`;
34 | }
35 |
36 | return entry;
37 | });
38 | }
39 |
40 | export function printDiagnostics(context: RollupContext, diagnostics: IDiagnostics[], pretty = true): void
41 | {
42 | diagnostics.forEach((diagnostic) =>
43 | {
44 | let print;
45 | let color;
46 | let category;
47 | switch (diagnostic.category)
48 | {
49 | case tsModule.DiagnosticCategory.Message:
50 | print = context.info;
51 | color = white;
52 | category = "";
53 | break;
54 | case tsModule.DiagnosticCategory.Error:
55 | print = context.error;
56 | color = red;
57 | category = "error";
58 | break;
59 | case tsModule.DiagnosticCategory.Warning:
60 | default:
61 | print = context.warn;
62 | color = yellow;
63 | category = "warning";
64 | break;
65 | }
66 |
67 | const type = diagnostic.type + " ";
68 |
69 | if (pretty)
70 | return print.call(context, `${diagnostic.formatted}`);
71 |
72 | if (diagnostic.fileLine !== undefined)
73 | return print.call(context, `${diagnostic.fileLine}: ${type}${category} TS${diagnostic.code}: ${color(diagnostic.flatMessage)}`);
74 |
75 | return print.call(context, `${type}${category} TS${diagnostic.code}: ${color(diagnostic.flatMessage)}`);
76 | });
77 | }
78 |
--------------------------------------------------------------------------------
/src/get-options-overrides.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | import * as tsTypes from "typescript";
3 | import { createFilter as createRollupFilter, normalizePath as normalize } from "@rollup/pluginutils";
4 |
5 | import { tsModule } from "./tsproxy";
6 | import { IOptions } from "./ioptions";
7 | import { RollupContext } from "./context";
8 |
9 | export function getOptionsOverrides({ useTsconfigDeclarationDir, cacheRoot }: IOptions, preParsedTsconfig?: tsTypes.ParsedCommandLine): tsTypes.CompilerOptions
10 | {
11 | const overrides: tsTypes.CompilerOptions = {
12 | noEmitHelpers: false,
13 | importHelpers: true,
14 | noResolve: false,
15 | noEmit: false,
16 | noEmitOnError: false,
17 | inlineSourceMap: false,
18 | outDir: normalize(`${cacheRoot}/placeholder`), // need an outdir that is different from source or tsconfig parsing trips up. https://github.com/Microsoft/TypeScript/issues/24715
19 | allowNonTsExtensions: true,
20 | };
21 |
22 | if (!preParsedTsconfig)
23 | return overrides;
24 |
25 | if (preParsedTsconfig.options.moduleResolution === tsModule.ModuleResolutionKind.Classic)
26 | overrides.moduleResolution = tsModule.ModuleResolutionKind.Node10;
27 | if (preParsedTsconfig.options.module === undefined)
28 | overrides.module = tsModule.ModuleKind.ES2015;
29 |
30 | // only set declarationDir if useTsconfigDeclarationDir is enabled
31 | if (!useTsconfigDeclarationDir)
32 | overrides.declarationDir = undefined;
33 |
34 | // unsetting sourceRoot if sourceMap is not enabled (in case original tsconfig had inlineSourceMap set that is being unset and would cause TS5051)
35 | const sourceMap = preParsedTsconfig.options.sourceMap;
36 | if (!sourceMap)
37 | overrides.sourceRoot = undefined;
38 |
39 | return overrides;
40 | }
41 |
42 | function expandIncludeWithDirs(include: string | string[], dirs: string[])
43 | {
44 | const newDirs: string[] = [];
45 |
46 | dirs.forEach(root => {
47 | if (include instanceof Array)
48 | include.forEach(x => newDirs.push(normalize(path.join(root, x))));
49 | else
50 | newDirs.push(normalize(path.join(root, include)));
51 | });
52 | return newDirs;
53 | }
54 |
55 | export function createFilter(context: RollupContext, pluginOptions: IOptions, parsedConfig: tsTypes.ParsedCommandLine)
56 | {
57 | let included = pluginOptions.include;
58 | let excluded = pluginOptions.exclude;
59 |
60 | if (parsedConfig.options.rootDirs)
61 | {
62 | included = expandIncludeWithDirs(included, parsedConfig.options.rootDirs);
63 | excluded = expandIncludeWithDirs(excluded, parsedConfig.options.rootDirs);
64 | }
65 |
66 | if (parsedConfig.projectReferences)
67 | {
68 | included = expandIncludeWithDirs(included, parsedConfig.projectReferences.map((x) => x.path)).concat(included);
69 | excluded = expandIncludeWithDirs(excluded, parsedConfig.projectReferences.map((x) => x.path)).concat(excluded);
70 | }
71 |
72 | context.debug(() => `included:\n${JSON.stringify(included, undefined, 4)}`);
73 | context.debug(() => `excluded:\n${JSON.stringify(excluded, undefined, 4)}`);
74 | return createRollupFilter(included, excluded, { resolve: parsedConfig.options.rootDir });
75 | }
76 |
--------------------------------------------------------------------------------
/src/host.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import { normalizePath as normalize } from "@rollup/pluginutils";
3 |
4 | import { tsModule } from "./tsproxy";
5 | import { TransformerFactoryCreator } from "./ioptions";
6 |
7 | export class LanguageServiceHost implements tsTypes.LanguageServiceHost
8 | {
9 | private snapshots: { [fileName: string]: tsTypes.IScriptSnapshot } = {};
10 | private versions: { [fileName: string]: number } = {};
11 | private service?: tsTypes.LanguageService;
12 | private fileNames: Set;
13 |
14 | constructor(private parsedConfig: tsTypes.ParsedCommandLine, private transformers: TransformerFactoryCreator[], private cwd: string)
15 | {
16 | this.fileNames = new Set(parsedConfig.fileNames);
17 | }
18 |
19 | public reset()
20 | {
21 | this.snapshots = {};
22 | this.versions = {};
23 | }
24 |
25 | public setLanguageService(service: tsTypes.LanguageService)
26 | {
27 | this.service = service;
28 | }
29 |
30 | public setSnapshot(fileName: string, source: string): tsTypes.IScriptSnapshot
31 | {
32 | fileName = normalize(fileName);
33 |
34 | const snapshot = tsModule.ScriptSnapshot.fromString(source);
35 | this.snapshots[fileName] = snapshot;
36 | this.versions[fileName] = (this.versions[fileName] || 0) + 1;
37 | this.fileNames.add(fileName);
38 | return snapshot;
39 | }
40 |
41 | public getScriptSnapshot(fileName: string): tsTypes.IScriptSnapshot | undefined
42 | {
43 | fileName = normalize(fileName);
44 |
45 | if (fileName in this.snapshots)
46 | return this.snapshots[fileName];
47 |
48 | const source = tsModule.sys.readFile(fileName);
49 | if (source)
50 | return this.setSnapshot(fileName, source);
51 |
52 | return undefined;
53 | }
54 |
55 | public getScriptFileNames = () => Array.from(this.fileNames.values());
56 |
57 | public getScriptVersion(fileName: string)
58 | {
59 | fileName = normalize(fileName);
60 |
61 | return (this.versions[fileName] || 0).toString();
62 | }
63 |
64 | public getCustomTransformers(): tsTypes.CustomTransformers | undefined
65 | {
66 | if (this.service === undefined || this.transformers === undefined || this.transformers.length === 0)
67 | return undefined;
68 |
69 | const transformer: tsTypes.CustomTransformers =
70 | {
71 | before: [],
72 | after: [],
73 | afterDeclarations: [],
74 | };
75 |
76 | for (const creator of this.transformers)
77 | {
78 | const factory = creator(this.service);
79 | if (factory.before)
80 | transformer.before = transformer.before!.concat(factory.before);
81 | if (factory.after)
82 | transformer.after = transformer.after!.concat(factory.after);
83 | if (factory.afterDeclarations)
84 | transformer.afterDeclarations = transformer.afterDeclarations!.concat(factory.afterDeclarations);
85 | }
86 |
87 | return transformer;
88 | }
89 |
90 | public getCompilationSettings = () => this.parsedConfig.options;
91 | public getTypeRootsVersion = () => 0;
92 | public getCurrentDirectory = () => this.cwd;
93 |
94 | public useCaseSensitiveFileNames = () => tsModule.sys.useCaseSensitiveFileNames;
95 | public getDefaultLibFileName = tsModule.getDefaultLibFilePath; // confusing naming: https://github.com/microsoft/TypeScript/issues/35318
96 |
97 | public readDirectory = tsModule.sys.readDirectory;
98 | public readFile = tsModule.sys.readFile;
99 | public fileExists = tsModule.sys.fileExists;
100 | public directoryExists = tsModule.sys.directoryExists;
101 | public getDirectories = tsModule.sys.getDirectories;
102 | public realpath = tsModule.sys.realpath!; // this exists in the default implementation: https://github.com/microsoft/TypeScript/blob/ab2523bbe0352d4486f67b73473d2143ad64d03d/src/compiler/sys.ts#L1288
103 |
104 | public trace = console.log;
105 | }
106 |
--------------------------------------------------------------------------------
/src/icache.ts:
--------------------------------------------------------------------------------
1 | export interface ICache
2 | {
3 | exists(name: string): boolean;
4 |
5 | path(name: string): string;
6 |
7 | match(names: string[]): boolean;
8 |
9 | read(name: string): DataType | null | undefined;
10 |
11 | write(name: string, data: DataType): void;
12 |
13 | touch(name: string): void;
14 |
15 | roll(): void;
16 | }
17 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { relative, dirname, normalize as pathNormalize, resolve } from "path";
2 | import * as tsTypes from "typescript";
3 | import { PluginImpl, InputOptions, TransformResult, SourceMap, Plugin } from "rollup";
4 | import { normalizePath as normalize } from "@rollup/pluginutils";
5 | import { blue, red, yellow, green } from "colors/safe";
6 | import { satisfies } from "semver";
7 | import findCacheDir from "find-cache-dir";
8 |
9 | import { RollupContext, VerbosityLevel } from "./context";
10 | import { LanguageServiceHost } from "./host";
11 | import { TsCache, convertEmitOutput, getAllReferences, ICode } from "./tscache";
12 | import { tsModule, setTypescriptModule } from "./tsproxy";
13 | import { IOptions } from "./ioptions";
14 | import { parseTsConfig } from "./parse-tsconfig";
15 | import { convertDiagnostic, printDiagnostics } from "./diagnostics";
16 | import { TSLIB, TSLIB_VIRTUAL, tslibSource, tslibVersion } from "./tslib";
17 | import { createFilter } from "./get-options-overrides";
18 |
19 | // these use globals during testing and are substituted by rollup-plugin-re during builds
20 | const TS_VERSION_RANGE = (global as any)?.rpt2__TS_VERSION_RANGE || "$TS_VERSION_RANGE";
21 | const ROLLUP_VERSION_RANGE = (global as any)?.rpt2__ROLLUP_VERSION_RANGE || "$ROLLUP_VERSION_RANGE";
22 | const RPT2_VERSION = (global as any)?.rpt2__ROLLUP_VERSION_RANGE || "$RPT2_VERSION";
23 |
24 | type RPT2Options = Partial;
25 |
26 | export { RPT2Options }
27 |
28 | const typescript: PluginImpl = (options) =>
29 | {
30 | let watchMode = false;
31 | let supportsThisLoad = false;
32 | let generateRound = 0;
33 | let rollupOptions: InputOptions;
34 | let context: RollupContext;
35 | let filter: ReturnType;
36 | let parsedConfig: tsTypes.ParsedCommandLine;
37 | let tsConfigPath: string | undefined;
38 | let servicesHost: LanguageServiceHost;
39 | let service: tsTypes.LanguageService;
40 | let documentRegistry: tsTypes.DocumentRegistry; // keep the same DocumentRegistry between watch cycles
41 | let cache: TsCache;
42 | let noErrors = true;
43 | let transformedFiles: Set;
44 | const declarations: { [name: string]: { type: tsTypes.OutputFile; map?: tsTypes.OutputFile } } = {};
45 | const checkedFiles = new Set();
46 |
47 | const getDiagnostics = (id: string, snapshot: tsTypes.IScriptSnapshot) =>
48 | {
49 | return cache.getSyntacticDiagnostics(id, snapshot, () =>
50 | {
51 | return service.getSyntacticDiagnostics(id);
52 | }).concat(cache.getSemanticDiagnostics(id, snapshot, () =>
53 | {
54 | return service.getSemanticDiagnostics(id);
55 | }));
56 | }
57 |
58 | const typecheckFile = (id: string, snapshot: tsTypes.IScriptSnapshot | undefined, tcContext: RollupContext) =>
59 | {
60 | if (!snapshot)
61 | return;
62 |
63 | id = normalize(id);
64 | checkedFiles.add(id); // must come before print, as that could bail
65 |
66 | const diagnostics = getDiagnostics(id, snapshot);
67 | printDiagnostics(tcContext, diagnostics, parsedConfig.options.pretty !== false);
68 |
69 | if (diagnostics.length > 0)
70 | noErrors = false;
71 | }
72 |
73 | const addDeclaration = (id: string, result: ICode) =>
74 | {
75 | if (!result.dts)
76 | return;
77 |
78 | const key = normalize(id);
79 | declarations[key] = { type: result.dts, map: result.dtsmap };
80 | context.debug(() => `${blue("generated declarations")} for '${key}'`);
81 | }
82 |
83 | /** common resolution check -- only resolve files that aren't declarations and pass `filter` */
84 | const shouldResolve = (id: string): boolean => {
85 | if (id.endsWith(".d.ts") || id.endsWith(".d.cts") || id.endsWith(".d.mts"))
86 | return false;
87 |
88 | if (!filter(id))
89 | return false;
90 |
91 | return true;
92 | }
93 |
94 | /** to be called at the end of Rollup's build phase, before output generation */
95 | const buildDone = (): void =>
96 | {
97 | if (!watchMode && !noErrors)
98 | context.info(yellow("there were errors or warnings."));
99 |
100 | cache?.done(); // if there's an initialization error in `buildStart`, such as a `tsconfig` error, the cache may not exist yet
101 | }
102 |
103 | const pluginOptions: IOptions = Object.assign({},
104 | {
105 | check: true,
106 | verbosity: VerbosityLevel.Warning,
107 | clean: false,
108 | cacheRoot: findCacheDir({ name: "rollup-plugin-typescript2" }),
109 | include: ["*.ts+(|x)", "**/*.ts+(|x)", "**/*.cts", "**/*.mts"],
110 | exclude: ["*.d.ts", "**/*.d.ts", "**/*.d.cts", "**/*.d.mts"],
111 | abortOnError: true,
112 | rollupCommonJSResolveHack: false,
113 | tsconfig: undefined,
114 | useTsconfigDeclarationDir: false,
115 | tsconfigOverride: {},
116 | transformers: [],
117 | tsconfigDefaults: {},
118 | objectHashIgnoreUnknownHack: false,
119 | cwd: process.cwd(),
120 | }, options as IOptions);
121 |
122 | if (!pluginOptions.typescript) {
123 | pluginOptions.typescript = require("typescript");
124 | }
125 | setTypescriptModule(pluginOptions.typescript);
126 | // eslint-disable-next-line prefer-const
127 | documentRegistry = tsModule.createDocumentRegistry();
128 |
129 | const self: Plugin = {
130 |
131 | name: "rpt2",
132 |
133 | options(config)
134 | {
135 | rollupOptions = { ...config };
136 | return config;
137 | },
138 |
139 | buildStart()
140 | {
141 | context = new RollupContext(pluginOptions.verbosity, pluginOptions.abortOnError, this, "rpt2: ");
142 |
143 | watchMode = process.env.ROLLUP_WATCH === "true" || !!this.meta.watchMode; // meta.watchMode was added in 2.14.0 to capture watch via Rollup API (i.e. no env var) (c.f. https://github.com/rollup/rollup/blob/master/CHANGELOG.md#2140)
144 | ({ parsedTsConfig: parsedConfig, fileName: tsConfigPath } = parseTsConfig(context, pluginOptions));
145 |
146 | // print out all versions and configurations
147 | context.info(`typescript version: ${tsModule.version}`);
148 | context.info(`tslib version: ${tslibVersion}`);
149 | context.info(`rollup version: ${this.meta.rollupVersion}`);
150 |
151 | if (!satisfies(tsModule.version, TS_VERSION_RANGE, { includePrerelease: true }))
152 | context.error(`Installed TypeScript version '${tsModule.version}' is outside of supported range '${TS_VERSION_RANGE}'`);
153 |
154 | if (!satisfies(this.meta.rollupVersion, ROLLUP_VERSION_RANGE, { includePrerelease: true }))
155 | context.error(`Installed Rollup version '${this.meta.rollupVersion}' is outside of supported range '${ROLLUP_VERSION_RANGE}'`);
156 |
157 | supportsThisLoad = satisfies(this.meta.rollupVersion, ">=2.60.0", { includePrerelease : true }); // this.load is 2.60.0+ only (c.f. https://github.com/rollup/rollup/blob/master/CHANGELOG.md#2600)
158 | if (!supportsThisLoad)
159 | context.warn(() => `${yellow("You are using a Rollup version '<2.60.0'")}. This may result in type-only files being ignored.`);
160 |
161 | context.info(`rollup-plugin-typescript2 version: ${RPT2_VERSION}`);
162 | context.debug(() => `plugin options:\n${JSON.stringify(pluginOptions, (key, value) => key === "typescript" ? `version ${(value as typeof tsModule).version}` : value, 4)}`);
163 | context.debug(() => `rollup config:\n${JSON.stringify(rollupOptions, undefined, 4)}`);
164 | context.debug(() => `tsconfig path: ${tsConfigPath}`);
165 |
166 | if (pluginOptions.objectHashIgnoreUnknownHack)
167 | context.warn(() => `${yellow("You are using 'objectHashIgnoreUnknownHack' option")}. If you enabled it because of async functions, try disabling it now.`);
168 |
169 | if (pluginOptions.rollupCommonJSResolveHack)
170 | context.warn(() => `${yellow("You are using 'rollupCommonJSResolveHack' option")}. This is no longer needed, try disabling it now.`);
171 |
172 | if (watchMode)
173 | context.info(`running in watch mode`);
174 |
175 | filter = createFilter(context, pluginOptions, parsedConfig);
176 |
177 | servicesHost = new LanguageServiceHost(parsedConfig, pluginOptions.transformers, pluginOptions.cwd);
178 | service = tsModule.createLanguageService(servicesHost, documentRegistry);
179 | servicesHost.setLanguageService(service);
180 |
181 | const runClean = pluginOptions.clean;
182 | const noCache = pluginOptions.clean || watchMode;
183 |
184 | cache = new TsCache(noCache, runClean, pluginOptions.objectHashIgnoreUnknownHack, servicesHost, pluginOptions.cacheRoot, parsedConfig.options, rollupOptions, parsedConfig.fileNames, context);
185 |
186 | // reset transformedFiles Set on each watch cycle
187 | transformedFiles = new Set();
188 |
189 | // printing compiler option errors
190 | if (pluginOptions.check) {
191 | const diagnostics = convertDiagnostic("options", service.getCompilerOptionsDiagnostics());
192 | printDiagnostics(context, diagnostics, parsedConfig.options.pretty !== false);
193 | if (diagnostics.length > 0)
194 | noErrors = false;
195 | }
196 | },
197 |
198 | watchChange(id)
199 | {
200 | const key = normalize(id);
201 | delete declarations[key];
202 | checkedFiles.delete(key);
203 | },
204 |
205 | resolveId(importee, importer)
206 | {
207 | if (importee === TSLIB)
208 | return TSLIB_VIRTUAL;
209 |
210 | if (!importer)
211 | return;
212 |
213 | importer = normalize(importer);
214 |
215 | // TODO: use module resolution cache
216 | const result = tsModule.nodeModuleNameResolver(importee, importer, parsedConfig.options, tsModule.sys);
217 | const resolved = result.resolvedModule?.resolvedFileName;
218 |
219 | if (!resolved)
220 | return;
221 |
222 | if (!shouldResolve(resolved))
223 | return;
224 |
225 | cache.setDependency(resolved, importer);
226 |
227 | context.debug(() => `${blue("resolving")} '${importee}' imported by '${importer}'`);
228 | context.debug(() => ` to '${resolved}'`);
229 |
230 | return pathNormalize(resolved); // use host OS separators to fix Windows issue: https://github.com/ezolenko/rollup-plugin-typescript2/pull/251
231 | },
232 |
233 | load(id)
234 | {
235 | if (id === TSLIB_VIRTUAL)
236 | return tslibSource;
237 |
238 | return null;
239 | },
240 |
241 | async transform(code, id)
242 | {
243 | transformedFiles.add(id); // note: this does not need normalization as we only compare Rollup <-> Rollup, and not Rollup <-> TS
244 |
245 | if (!filter(id))
246 | return undefined;
247 |
248 | const snapshot = servicesHost.setSnapshot(id, code);
249 |
250 | // getting compiled file from cache or from ts
251 | const result = cache.getCompiled(id, snapshot, () =>
252 | {
253 | const output = service.getEmitOutput(id);
254 |
255 | if (output.emitSkipped)
256 | {
257 | noErrors = false;
258 | // always checking on fatal errors, even if options.check is set to false
259 | typecheckFile(id, snapshot, context);
260 | // since no output was generated, aborting compilation
261 | this.error(red(`Emit skipped for '${id}'. See https://github.com/microsoft/TypeScript/issues/49790 for potential reasons why this may occur`));
262 | }
263 |
264 | const references = getAllReferences(id, snapshot, parsedConfig.options);
265 | return convertEmitOutput(output, references);
266 | });
267 |
268 | if (pluginOptions.check)
269 | typecheckFile(id, snapshot, context);
270 |
271 | if (!result)
272 | return undefined;
273 |
274 | if (watchMode && result.references)
275 | {
276 | if (tsConfigPath)
277 | this.addWatchFile(tsConfigPath);
278 |
279 | result.references.map(this.addWatchFile, this);
280 | context.debug(() => `${green(" watching")}: ${result.references!.join("\nrpt2: ")}`);
281 | }
282 |
283 | addDeclaration(id, result);
284 |
285 | // handle all type-only imports by resolving + loading all of TS's references
286 | // Rollup can't see these otherwise, because they are "emit-less" and produce no JS
287 | if (result.references && supportsThisLoad) {
288 | for (const ref of result.references) {
289 | // pre-emptively filter out files that we don't resolve ourselves (e.g. declarations). don't add new files to Rollup's pipeline if we can't resolve them
290 | if (!shouldResolve(ref))
291 | continue;
292 |
293 | const module = await this.resolve(ref, id);
294 | if (!module || transformedFiles.has(module.id)) // check for circular references (per https://rollupjs.org/guide/en/#thisload)
295 | continue;
296 |
297 | // wait for all to be loaded (otherwise, as this is async, some may end up only loading after `generateBundle`)
298 | await this.load({id: module.id});
299 | }
300 | }
301 |
302 | // if a user sets this compilerOption, they probably want another plugin (e.g. Babel, ESBuild) to transform their TS instead, while rpt2 just type-checks and/or outputs declarations
303 | // note that result.code is non-existent if emitDeclarationOnly per https://github.com/ezolenko/rollup-plugin-typescript2/issues/268
304 | if (parsedConfig.options.emitDeclarationOnly)
305 | {
306 | context.debug(() => `${blue("emitDeclarationOnly")} enabled, not transforming TS`);
307 | return undefined;
308 | }
309 |
310 | const transformResult: TransformResult = { code: result.code, map: { mappings: "" } };
311 |
312 | if (result.map)
313 | {
314 | pluginOptions.sourceMapCallback?.(id, result.map);
315 | transformResult.map = JSON.parse(result.map);
316 | }
317 |
318 | return transformResult;
319 | },
320 |
321 | buildEnd(err)
322 | {
323 | generateRound = 0; // in watch mode, buildEnd resets generate count just before generateBundle for each output
324 |
325 | if (err)
326 | {
327 | buildDone();
328 | // workaround: err.stack contains err.message and Rollup prints both, causing duplication, so split out the stack itself if it exists (c.f. https://github.com/ezolenko/rollup-plugin-typescript2/issues/103#issuecomment-1172820658)
329 | const stackOnly = err.stack?.split(err.message)[1];
330 | if (stackOnly)
331 | this.error({ ...err, message: err.message, stack: stackOnly });
332 | else
333 | this.error(err);
334 | }
335 |
336 | if (!pluginOptions.check)
337 | return buildDone();
338 |
339 | // walkTree once on each cycle when in watch mode
340 | if (watchMode)
341 | {
342 | cache.walkTree((id) =>
343 | {
344 | if (!filter(id))
345 | return;
346 |
347 | const snapshot = servicesHost.getScriptSnapshot(id);
348 | typecheckFile(id, snapshot, context);
349 | });
350 | }
351 |
352 | // type-check missed files as well
353 | parsedConfig.fileNames.forEach((name) =>
354 | {
355 | const key = normalize(name);
356 | if (checkedFiles.has(key) || !filter(key)) // don't duplicate if it's already been checked
357 | return;
358 |
359 | context.debug(() => `type-checking missed '${key}'`);
360 | const snapshot = servicesHost.getScriptSnapshot(key);
361 | typecheckFile(key, snapshot, context);
362 | });
363 |
364 | buildDone();
365 | },
366 |
367 | generateBundle(this, _output)
368 | {
369 | context.debug(() => `generating target ${generateRound + 1}`);
370 | generateRound++;
371 |
372 | if (!parsedConfig.options.declaration)
373 | return;
374 |
375 | parsedConfig.fileNames.forEach((name) =>
376 | {
377 | const key = normalize(name);
378 | if (key in declarations || !filter(key))
379 | return;
380 |
381 | context.debug(() => `generating missed declarations for '${key}'`);
382 | const out = convertEmitOutput(service.getEmitOutput(key, true));
383 | addDeclaration(key, out);
384 | });
385 |
386 | const emitDeclaration = (key: string, extension: string, entry?: tsTypes.OutputFile) =>
387 | {
388 | if (!entry)
389 | return;
390 |
391 | let fileName = entry.name;
392 | if (fileName.includes("?")) // HACK for rollup-plugin-vue, it creates virtual modules in form 'file.vue?rollup-plugin-vue=script.ts'
393 | fileName = fileName.split("?", 1) + extension;
394 |
395 | // If 'useTsconfigDeclarationDir' is in plugin options, directly write to 'declarationDir'.
396 | // This may not be under Rollup's output directory, and thus can't be emitted as an asset.
397 | if (pluginOptions.useTsconfigDeclarationDir)
398 | {
399 | context.debug(() => `${blue("emitting declarations")} for '${key}' to '${fileName}'`);
400 | tsModule.sys.writeFile(fileName, entry.text, entry.writeByteOrderMark);
401 | return;
402 | }
403 |
404 | // don't mutate the entry because generateBundle gets called multiple times
405 | let entryText = entry.text
406 | const cachePlaceholder = `${pluginOptions.cacheRoot}/placeholder`
407 |
408 | // modify declaration map sources to correct relative path (only if outputting)
409 | if (extension === ".d.ts.map" && (_output?.file || _output?.dir))
410 | {
411 | const declarationDir = (_output.file ? dirname(_output.file) : _output.dir) as string;
412 | const parsedText = JSON.parse(entryText) as SourceMap;
413 | // invert back to absolute, then make relative to declarationDir
414 | parsedText.sources = parsedText.sources.map(source =>
415 | {
416 | const absolutePath = resolve(cachePlaceholder, source);
417 | return normalize(relative(declarationDir, absolutePath));
418 | });
419 | entryText = JSON.stringify(parsedText);
420 | }
421 |
422 | const relativePath = normalize(relative(cachePlaceholder, fileName));
423 | context.debug(() => `${blue("emitting declarations")} for '${key}' to '${relativePath}'`);
424 | this.emitFile({
425 | type: "asset",
426 | source: entryText,
427 | fileName: relativePath,
428 | });
429 | };
430 |
431 | Object.keys(declarations).forEach((key) =>
432 | {
433 | const { type, map } = declarations[key];
434 | emitDeclaration(key, ".d.ts", type);
435 | emitDeclaration(key, ".d.ts.map", map);
436 | });
437 | },
438 | };
439 |
440 | return self;
441 | };
442 |
443 | export default typescript;
444 |
--------------------------------------------------------------------------------
/src/ioptions.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 |
3 | import { tsModule } from "./tsproxy";
4 |
5 | export interface ICustomTransformer
6 | {
7 | before?: tsTypes.TransformerFactory;
8 | after?: tsTypes.TransformerFactory;
9 | afterDeclarations?: tsTypes.TransformerFactory;
10 | }
11 |
12 | export type TransformerFactoryCreator = (ls: tsTypes.LanguageService) => tsTypes.CustomTransformers | ICustomTransformer;
13 |
14 | export interface IOptions
15 | {
16 | cwd: string;
17 | include: string|string[];
18 | exclude: string|string[];
19 | check: boolean;
20 | verbosity: number;
21 | clean: boolean;
22 | cacheRoot: string;
23 | abortOnError: boolean;
24 | rollupCommonJSResolveHack: boolean;
25 | tsconfig?: string;
26 | useTsconfigDeclarationDir: boolean;
27 | typescript: typeof tsModule;
28 | tsconfigOverride: any;
29 | transformers: TransformerFactoryCreator[];
30 | tsconfigDefaults: any;
31 | sourceMapCallback: (id: string, map: string) => void;
32 | objectHashIgnoreUnknownHack: boolean;
33 | }
34 |
--------------------------------------------------------------------------------
/src/parse-tsconfig.ts:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import * as _ from "lodash";
3 |
4 | import { tsModule } from "./tsproxy";
5 | import { RollupContext } from "./context";
6 | import { convertDiagnostic, printDiagnostics } from "./diagnostics";
7 | import { getOptionsOverrides } from "./get-options-overrides";
8 | import { IOptions } from "./ioptions";
9 |
10 | export function parseTsConfig(context: RollupContext, pluginOptions: IOptions)
11 | {
12 | const fileName = tsModule.findConfigFile(pluginOptions.cwd, tsModule.sys.fileExists, pluginOptions.tsconfig);
13 |
14 | // if the value was provided, but no file, fail hard
15 | if (pluginOptions.tsconfig !== undefined && !fileName)
16 | context.error(`failed to open '${pluginOptions.tsconfig}'`);
17 |
18 | let loadedConfig: any = {};
19 | let baseDir = pluginOptions.cwd;
20 | let configFileName;
21 | let pretty = true;
22 | if (fileName)
23 | {
24 | const text = tsModule.sys.readFile(fileName)!; // readFile only returns undefined when the file doesn't exist, which we already checked above
25 | const result = tsModule.parseConfigFileTextToJson(fileName, text);
26 | pretty = result.config?.pretty ?? pretty;
27 |
28 | if (result.error !== undefined)
29 | {
30 | printDiagnostics(context, convertDiagnostic("config", [result.error]), pretty);
31 | context.error(`failed to parse '${fileName}'`);
32 | }
33 |
34 | loadedConfig = result.config;
35 | baseDir = dirname(fileName);
36 | configFileName = fileName;
37 | }
38 |
39 | const mergedConfig = {};
40 | _.merge(mergedConfig, pluginOptions.tsconfigDefaults, loadedConfig, pluginOptions.tsconfigOverride);
41 |
42 | const preParsedTsConfig = tsModule.parseJsonConfigFileContent(mergedConfig, tsModule.sys, baseDir, getOptionsOverrides(pluginOptions), configFileName);
43 | const compilerOptionsOverride = getOptionsOverrides(pluginOptions, preParsedTsConfig);
44 | const parsedTsConfig = tsModule.parseJsonConfigFileContent(mergedConfig, tsModule.sys, baseDir, compilerOptionsOverride, configFileName);
45 |
46 | const module = parsedTsConfig.options.module!;
47 | if (module !== tsModule.ModuleKind.ES2015 && module !== tsModule.ModuleKind.ES2020 && module !== tsModule.ModuleKind.ES2022 && module !== tsModule.ModuleKind.ESNext)
48 | context.error(`Incompatible tsconfig option. Module resolves to '${tsModule.ModuleKind[module]}'. This is incompatible with Rollup, please use 'module: "ES2015"', 'module: "ES2020"', 'module: "ES2022"', or 'module: "ESNext"'.`);
49 |
50 | printDiagnostics(context, convertDiagnostic("config", parsedTsConfig.errors), pretty);
51 |
52 | context.debug(`built-in options overrides: ${JSON.stringify(compilerOptionsOverride, undefined, 4)}`);
53 | context.debug(`parsed tsconfig: ${JSON.stringify(parsedTsConfig, undefined, 4)}`);
54 |
55 | return { parsedTsConfig, fileName };
56 | }
57 |
--------------------------------------------------------------------------------
/src/rollingcache.ts:
--------------------------------------------------------------------------------
1 | import { existsSync, readdirSync, renameSync } from "fs";
2 | import { emptyDirSync, ensureFileSync, readJsonSync, removeSync, writeJsonSync } from "fs-extra";
3 | import * as _ from "lodash";
4 |
5 | import { ICache } from "./icache";
6 |
7 | /**
8 | * Saves data in new cache folder or reads it from old one.
9 | * Avoids perpetually growing cache and situations when things need to consider changed and then reverted data to be changed.
10 | */
11 | export class RollingCache implements ICache
12 | {
13 | private oldCacheRoot: string;
14 | private newCacheRoot: string;
15 | private rolled: boolean = false;
16 |
17 | /** @param cacheRoot: root folder for the cache */
18 | constructor(private cacheRoot: string)
19 | {
20 | this.oldCacheRoot = `${this.cacheRoot}/cache`;
21 | this.newCacheRoot = `${this.cacheRoot}/cache_`;
22 | emptyDirSync(this.newCacheRoot);
23 | }
24 |
25 | /** @returns true if name exists in either old cache or new cache */
26 | public exists(name: string): boolean
27 | {
28 | if (this.rolled)
29 | return false;
30 |
31 | if (existsSync(`${this.newCacheRoot}/${name}`))
32 | return true;
33 |
34 | return existsSync(`${this.oldCacheRoot}/${name}`);
35 | }
36 |
37 | public path(name: string): string
38 | {
39 | return `${this.oldCacheRoot}/${name}`;
40 | }
41 |
42 | /** @returns true if old cache contains all names and nothing more */
43 | public match(names: string[]): boolean
44 | {
45 | if (this.rolled)
46 | return false;
47 |
48 | if (!existsSync(this.oldCacheRoot))
49 | return names.length === 0; // empty folder matches
50 |
51 | return _.isEqual(readdirSync(this.oldCacheRoot).sort(), names.sort());
52 | }
53 |
54 | /** @returns data for name, must exist in either old cache or new cache */
55 | public read(name: string): DataType | null | undefined
56 | {
57 | if (existsSync(`${this.newCacheRoot}/${name}`))
58 | return readJsonSync(`${this.newCacheRoot}/${name}`, { encoding: "utf8", throws: false });
59 |
60 | return readJsonSync(`${this.oldCacheRoot}/${name}`, { encoding: "utf8", throws: false });
61 | }
62 |
63 | public write(name: string, data: DataType): void
64 | {
65 | if (this.rolled)
66 | return;
67 |
68 | if (data === undefined)
69 | return;
70 |
71 | writeJsonSync(`${this.newCacheRoot}/${name}`, data);
72 | }
73 |
74 | public touch(name: string)
75 | {
76 | if (this.rolled)
77 | return;
78 |
79 | ensureFileSync(`${this.newCacheRoot}/${name}`);
80 | }
81 |
82 | /** clears old cache and moves new in its place */
83 | public roll()
84 | {
85 | if (this.rolled)
86 | return;
87 |
88 | this.rolled = true;
89 | removeSync(this.oldCacheRoot);
90 | if (existsSync(this.newCacheRoot)) {
91 | renameSync(this.newCacheRoot, this.oldCacheRoot);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/tscache.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 | import * as fs from "fs-extra";
3 | import * as _ from "lodash";
4 | import { Graph, alg } from "graphlib";
5 | import objHash from "object-hash";
6 | import { blue, yellow, green } from "colors/safe";
7 |
8 | import { RollupContext } from "./context";
9 | import { RollingCache } from "./rollingcache";
10 | import { ICache } from "./icache";
11 | import { tsModule } from "./tsproxy";
12 | import { IDiagnostics, convertDiagnostic } from "./diagnostics";
13 |
14 | export interface ICode
15 | {
16 | code: string;
17 | map?: string;
18 | dts?: tsTypes.OutputFile;
19 | dtsmap?: tsTypes.OutputFile;
20 | references?: string[];
21 | }
22 |
23 | interface INodeLabel
24 | {
25 | dirty: boolean;
26 | }
27 |
28 | interface ITypeSnapshot
29 | {
30 | id: string;
31 | snapshot: tsTypes.IScriptSnapshot | undefined;
32 | }
33 |
34 | export function convertEmitOutput(output: tsTypes.EmitOutput, references?: string[]): ICode
35 | {
36 | const out: ICode = { code: "", references };
37 |
38 | output.outputFiles.forEach((e) =>
39 | {
40 | if (e.name.endsWith(".d.ts"))
41 | out.dts = e;
42 | else if (e.name.endsWith(".d.ts.map"))
43 | out.dtsmap = e;
44 | else if (e.name.endsWith(".map"))
45 | out.map = e.text;
46 | else
47 | out.code = e.text;
48 | });
49 |
50 | return out;
51 | }
52 |
53 | export function getAllReferences(importer: string, snapshot: tsTypes.IScriptSnapshot | undefined, options: tsTypes.CompilerOptions)
54 | {
55 | if (!snapshot)
56 | return [];
57 |
58 | const info = tsModule.preProcessFile(snapshot.getText(0, snapshot.getLength()), true, true);
59 |
60 | return _.compact(info.referencedFiles.concat(info.importedFiles).map((reference) =>
61 | {
62 | const resolved = tsModule.nodeModuleNameResolver(reference.fileName, importer, options, tsModule.sys);
63 | return resolved.resolvedModule?.resolvedFileName;
64 | }));
65 | }
66 |
67 | export class TsCache
68 | {
69 | private cacheVersion = "9";
70 | private cachePrefix = "rpt2_";
71 | private dependencyTree: Graph;
72 | private ambientTypes!: ITypeSnapshot[];
73 | private ambientTypesDirty = false;
74 | private cacheDir!: string;
75 | private codeCache!: ICache;
76 | private typesCache!: ICache;
77 | private semanticDiagnosticsCache!: ICache;
78 | private syntacticDiagnosticsCache!: ICache;
79 | private hashOptions = { algorithm: "sha1", ignoreUnknown: false };
80 |
81 | constructor(private noCache: boolean, runClean: boolean, hashIgnoreUnknown: boolean, private host: tsTypes.LanguageServiceHost, private cacheRoot: string, private options: tsTypes.CompilerOptions, private rollupConfig: any, rootFilenames: string[], private context: RollupContext)
82 | {
83 | this.dependencyTree = new Graph({ directed: true });
84 | this.dependencyTree.setDefaultNodeLabel(() => ({ dirty: false }));
85 |
86 | if (runClean)
87 | this.clean();
88 |
89 | if (noCache)
90 | return;
91 |
92 | this.hashOptions.ignoreUnknown = hashIgnoreUnknown;
93 | this.cacheDir = `${this.cacheRoot}/${this.cachePrefix}${objHash(
94 | {
95 | version: this.cacheVersion,
96 | rootFilenames,
97 | options: this.options,
98 | rollupConfig: this.rollupConfig,
99 | tsVersion: tsModule.version,
100 | },
101 | this.hashOptions,
102 | )}`;
103 |
104 | this.init();
105 |
106 | const automaticTypes = tsModule.getAutomaticTypeDirectiveNames(options, tsModule.sys)
107 | .map((entry) => tsModule.resolveTypeReferenceDirective(entry, undefined, options, tsModule.sys))
108 | .filter((entry) => entry.resolvedTypeReferenceDirective?.resolvedFileName)
109 | .map((entry) => entry.resolvedTypeReferenceDirective!.resolvedFileName!);
110 |
111 | this.ambientTypes = rootFilenames.filter(file => file.endsWith(".d.ts"))
112 | .concat(automaticTypes)
113 | .map((id) => ({ id, snapshot: this.host.getScriptSnapshot(id) }));
114 |
115 | this.checkAmbientTypes();
116 | }
117 |
118 | private clean()
119 | {
120 | if (!fs.pathExistsSync(this.cacheRoot))
121 | return;
122 |
123 | const entries = fs.readdirSync(this.cacheRoot);
124 | entries.forEach((e) =>
125 | {
126 | const dir = `${this.cacheRoot}/${e}`;
127 |
128 | /* istanbul ignore if -- this is a safety check, but shouldn't happen when using a dedicated cache dir */
129 | if (!e.startsWith(this.cachePrefix))
130 | {
131 | this.context.debug(`skipping cleaning '${dir}' as it does not have prefix '${this.cachePrefix}'`);
132 | return;
133 | }
134 |
135 | /* istanbul ignore if -- this is a safety check, but should never happen in normal usage */
136 | if (!fs.statSync(dir).isDirectory)
137 | {
138 | this.context.debug(`skipping cleaning '${dir}' as it is not a directory`);
139 | return;
140 | }
141 |
142 | this.context.info(blue(`cleaning cache: ${dir}`));
143 | fs.removeSync(`${dir}`);
144 | });
145 | }
146 |
147 | public setDependency(importee: string, importer: string): void
148 | {
149 | // importee -> importer
150 | this.context.debug(`${blue("dependency")} '${importee}'`);
151 | this.context.debug(` imported by '${importer}'`);
152 | this.dependencyTree.setEdge(importer, importee);
153 | }
154 |
155 | public walkTree(cb: (id: string) => void | false): void
156 | {
157 | if (alg.isAcyclic(this.dependencyTree))
158 | return alg.topsort(this.dependencyTree).forEach(id => cb(id));
159 |
160 | this.context.info(yellow("import tree has cycles"));
161 | this.dependencyTree.nodes().forEach(id => cb(id));
162 | }
163 |
164 | public done()
165 | {
166 | if (this.noCache)
167 | return;
168 |
169 | this.context.info(blue("rolling caches"));
170 | this.codeCache.roll();
171 | this.semanticDiagnosticsCache.roll();
172 | this.syntacticDiagnosticsCache.roll();
173 | this.typesCache.roll();
174 | }
175 |
176 | public getCompiled(id: string, snapshot: tsTypes.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined
177 | {
178 | this.context.info(`${blue("transpiling")} '${id}'`);
179 | // if !isolatedModules, compiled JS code can change if its imports do (e.g. enums). also, declarations can change based on imports as well
180 | return this.getCached(this.codeCache, id, snapshot, Boolean(!this.options.isolatedModules || this.options.declaration), transform);
181 | }
182 |
183 | public getSyntacticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[]
184 | {
185 | return this.getDiagnostics("syntax", this.syntacticDiagnosticsCache, id, snapshot, check);
186 | }
187 |
188 | public getSemanticDiagnostics(id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[]
189 | {
190 | return this.getDiagnostics("semantic", this.semanticDiagnosticsCache, id, snapshot, check);
191 | }
192 |
193 | private checkAmbientTypes(): void
194 | {
195 | this.context.debug(blue("Ambient types:"));
196 | const typeHashes = this.ambientTypes.filter((snapshot) => snapshot.snapshot !== undefined)
197 | .map((snapshot) =>
198 | {
199 | this.context.debug(` ${snapshot.id}`);
200 | return this.createHash(snapshot.id, snapshot.snapshot!);
201 | });
202 | // types dirty if any d.ts changed, added or removed
203 | this.ambientTypesDirty = !this.typesCache.match(typeHashes);
204 |
205 | if (this.ambientTypesDirty)
206 | this.context.info(yellow("ambient types changed, redoing all semantic diagnostics"));
207 |
208 | typeHashes.forEach(this.typesCache.touch, this.typesCache);
209 | }
210 |
211 | private getDiagnostics(type: string, cache: ICache, id: string, snapshot: tsTypes.IScriptSnapshot, check: () => tsTypes.Diagnostic[]): IDiagnostics[]
212 | {
213 | // don't need to check imports for syntactic diagnostics (per https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API#design-goals)
214 | return this.getCached(cache, id, snapshot, type === "semantic", () => convertDiagnostic(type, check()));
215 | }
216 |
217 | private getCached(cache: ICache, id: string, snapshot: tsTypes.IScriptSnapshot, checkImports: boolean, convert: () => CacheType): CacheType
218 | {
219 | if (this.noCache)
220 | return convert();
221 |
222 | const hash = this.createHash(id, snapshot);
223 | this.context.debug(` cache: '${cache.path(hash)}'`);
224 |
225 | if (cache.exists(hash) && !this.isDirty(id, checkImports))
226 | {
227 | this.context.debug(green(" cache hit"));
228 |
229 | const data = cache.read(hash);
230 | if (data)
231 | {
232 | cache.write(hash, data);
233 | return data;
234 | }
235 | else /* istanbul ignore next -- should only happen when corrupted cache */
236 | this.context.warn(yellow(" cache broken, discarding"));
237 | }
238 |
239 | this.context.debug(yellow(" cache miss"));
240 |
241 | const convertedData = convert();
242 | cache.write(hash, convertedData);
243 | this.markAsDirty(id);
244 | return convertedData;
245 | }
246 |
247 | private init()
248 | {
249 | this.codeCache = new RollingCache(`${this.cacheDir}/code`);
250 | this.typesCache = new RollingCache(`${this.cacheDir}/types`);
251 | this.syntacticDiagnosticsCache = new RollingCache(`${this.cacheDir}/syntacticDiagnostics`);
252 | this.semanticDiagnosticsCache = new RollingCache(`${this.cacheDir}/semanticDiagnostics`);
253 | }
254 |
255 | private markAsDirty(id: string): void
256 | {
257 | this.dependencyTree.setNode(id, { dirty: true });
258 | }
259 |
260 | /** @returns true if node, any of its imports, or any ambient types changed */
261 | private isDirty(id: string, checkImports: boolean): boolean
262 | {
263 | const label = this.dependencyTree.node(id) as INodeLabel;
264 |
265 | if (!label)
266 | return false;
267 |
268 | if (!checkImports || label.dirty)
269 | return label.dirty;
270 |
271 | if (this.ambientTypesDirty)
272 | return true;
273 |
274 | const dependencies = alg.dijkstra(this.dependencyTree, id);
275 |
276 | return Object.keys(dependencies).some(node =>
277 | {
278 | const dependency = dependencies[node];
279 | if (!node || dependency.distance === Infinity)
280 | return false;
281 |
282 | const l = this.dependencyTree.node(node) as INodeLabel | undefined;
283 | const dirty = l === undefined ? true : l.dirty;
284 |
285 | if (dirty)
286 | this.context.debug(` import changed: ${node}`);
287 |
288 | return dirty;
289 | });
290 | }
291 |
292 | /** @returns an FS-safe hash string for use as a path to the cached content */
293 | private createHash(id: string, snapshot: tsTypes.IScriptSnapshot)
294 | {
295 | const data = snapshot.getText(0, snapshot.getLength());
296 | return objHash({ data, id }, this.hashOptions);
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/src/tslib.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from "fs";
2 |
3 | // The injected id for helpers.
4 | export const TSLIB = "tslib";
5 | export const TSLIB_VIRTUAL = "\0tslib.js";
6 | export let tslibSource: string;
7 | export let tslibVersion: string;
8 |
9 | try
10 | {
11 | // eslint-disable-next-line @typescript-eslint/no-var-requires
12 | const tslibPackage = require("tslib/package.json");
13 | const tslibPath = require.resolve("tslib/" + tslibPackage.module);
14 | tslibSource = readFileSync(tslibPath, "utf8");
15 | tslibVersion = tslibPackage.version;
16 | } catch (e)
17 | {
18 | console.warn("rpt2: Error loading `tslib` helper library.");
19 | throw e;
20 | }
21 |
--------------------------------------------------------------------------------
/src/tsproxy.ts:
--------------------------------------------------------------------------------
1 | import * as tsTypes from "typescript";
2 |
3 | export let tsModule: typeof tsTypes;
4 |
5 | export function setTypescriptModule(override: typeof tsTypes)
6 | {
7 | tsModule = override;
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "declarationMap": true,
5 | },
6 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "module": "ES2020",
6 | "sourceMap": true,
7 | "noUnusedParameters": true,
8 | "noUnusedLocals": true,
9 | "diagnostics": true,
10 | "listFiles": true,
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "strict": true,
15 | "outDir": "./build",
16 | "allowSyntheticDefaultImports": true
17 | },
18 | "include": [
19 | "src/**/*.ts"
20 | ],
21 | "exclude": []
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "compilerOptions": {
4 | "esModuleInterop": true, // needed to parse some imports with ts-jest (since Rollup isn't used when importing during testing)
5 | },
6 | }
7 |
--------------------------------------------------------------------------------