|
| 1 | +const cjsContextCheck = 'typeof module !== \'undefined\''; |
| 2 | +const tsCheck = '1 as number'; |
| 3 | + |
| 4 | +const declareReact = ` |
| 5 | +const React = { |
| 6 | + createElement: (...args) => Array.from(args), |
| 7 | +}; |
| 8 | +`; |
| 9 | +const jsxCheck = '<><div>JSX</div></>'; |
| 10 | + |
| 11 | +const preserveName = ` |
| 12 | +assert( |
| 13 | + (function functionName() {}).name === 'functionName', |
| 14 | + 'Name should be preserved' |
| 15 | +); |
| 16 | +`; |
| 17 | + |
| 18 | +const syntaxLowering = ` |
| 19 | +// es2016 - Exponentiation operator |
| 20 | +10 ** 4; |
| 21 | +
|
| 22 | +// es2017 - Async functions |
| 23 | +(async () => {}); |
| 24 | +
|
| 25 | +// es2018 - Spread properties |
| 26 | +({...Object}); |
| 27 | +
|
| 28 | +// es2018 - Rest properties |
| 29 | +const {...x} = Object; |
| 30 | +
|
| 31 | +// es2019 - Optional catch binding |
| 32 | +try {} catch {} |
| 33 | +
|
| 34 | +// es2020 - Optional chaining |
| 35 | +Object?.keys; |
| 36 | +
|
| 37 | +// es2020 - Nullish coalescing |
| 38 | +Object ?? true |
| 39 | +
|
| 40 | +// es2020 - import.meta |
| 41 | +// import.meta |
| 42 | +
|
| 43 | +// es2021 - Logical assignment operators |
| 44 | +// let a = false; a ??= true; a ||= true; a &&= true; |
| 45 | +
|
| 46 | +// es2022 - Class instance fields |
| 47 | +(class { x }); |
| 48 | +
|
| 49 | +// es2022 - Static class fields |
| 50 | +(class { static x }); |
| 51 | +
|
| 52 | +// es2022 - Private instance methods |
| 53 | +(class { #x() {} }); |
| 54 | +
|
| 55 | +// es2022 - Private instance fields |
| 56 | +(class { #x }); |
| 57 | +
|
| 58 | +// es2022 - Private static methods |
| 59 | +(class { static #x() {} }); |
| 60 | +
|
| 61 | +// es2022 - Private static fields |
| 62 | +(class { static #x }); |
| 63 | +
|
| 64 | +// es2022 - Class static blocks |
| 65 | +(class { static {} }); |
| 66 | +
|
| 67 | +export const named = 2; |
| 68 | +export default 1; |
| 69 | +`; |
| 70 | + |
| 71 | +const sourcemap = { |
| 72 | + // Adding the dynamic import helps test the import transformation's source map |
| 73 | + test: ( |
| 74 | + extension: string, |
| 75 | + ) => `import ('node:fs');\nconst { stack } = new Error(); const searchString = 'index.${extension}:SOURCEMAP_LINE'; assert(stack.includes(searchString), \`Expected \${searchString} in stack: \${stack}\`)`, |
| 76 | + tag: ( |
| 77 | + strings: TemplateStringsArray, |
| 78 | + ...values: string[] |
| 79 | + ) => { |
| 80 | + const finalString = String.raw({ raw: strings }, ...values); |
| 81 | + const lineNumber = finalString.split('\n').findIndex(line => line.includes('SOURCEMAP_LINE')) + 1; |
| 82 | + return finalString.replaceAll('SOURCEMAP_LINE', lineNumber.toString()); |
| 83 | + }, |
| 84 | +}; |
| 85 | + |
| 86 | +export const files = { |
| 87 | + 'js/index.js': ` |
| 88 | + import assert from 'assert'; |
| 89 | + ${syntaxLowering} |
| 90 | + ${preserveName} |
| 91 | + export const cjsContext = ${cjsContextCheck}; |
| 92 | + `, |
| 93 | + |
| 94 | + 'json/index.json': JSON.stringify({ loaded: 'json' }), |
| 95 | + |
| 96 | + 'cjs/index.cjs': sourcemap.tag` |
| 97 | + const assert = require('node:assert'); |
| 98 | + assert(${cjsContextCheck}, 'Should have CJS context'); |
| 99 | + ${preserveName} |
| 100 | + ${sourcemap.test('cjs')} |
| 101 | +
|
| 102 | + // Assert __esModule is unwrapped |
| 103 | + import ('../ts/index.ts').then((m) => assert( |
| 104 | + !(typeof m.default === 'object' && ('default' in m.default)), |
| 105 | + )); |
| 106 | + exports.named = 'named'; |
| 107 | + `, |
| 108 | + |
| 109 | + 'mjs/index.mjs': ` |
| 110 | + import assert from 'assert'; |
| 111 | + export const mjsHasCjsContext = ${cjsContextCheck}; |
| 112 | +
|
| 113 | + import ('pkg-commonjs').then((m) => assert( |
| 114 | + !(typeof m.default === 'object' && ('default' in m.default)), |
| 115 | + )); |
| 116 | + `, |
| 117 | + |
| 118 | + 'ts/index.ts': sourcemap.tag` |
| 119 | + import assert from 'assert'; |
| 120 | + import type {Type} from 'resolved-by-tsc' |
| 121 | +
|
| 122 | + interface Foo {} |
| 123 | +
|
| 124 | + type Foo = number |
| 125 | +
|
| 126 | + declare module 'foo' {} |
| 127 | +
|
| 128 | + enum BasicEnum { |
| 129 | + Left, |
| 130 | + Right, |
| 131 | + } |
| 132 | +
|
| 133 | + enum NamedEnum { |
| 134 | + SomeEnum = 'some-value', |
| 135 | + } |
| 136 | +
|
| 137 | + export const a = BasicEnum.Left; |
| 138 | +
|
| 139 | + export const b = NamedEnum.SomeEnum; |
| 140 | +
|
| 141 | + export default function foo(): string { |
| 142 | + return 'foo' |
| 143 | + } |
| 144 | +
|
| 145 | + // For "ts as tsx" test |
| 146 | + const bar = <T>(value: T) => fn<T>(); |
| 147 | +
|
| 148 | + ${preserveName} |
| 149 | + ${sourcemap.test('ts')} |
| 150 | + export const cjsContext = ${cjsContextCheck}; |
| 151 | + ${tsCheck}; |
| 152 | + `, |
| 153 | + |
| 154 | + // TODO: test resolution priority for files 'index.tsx` & 'index.tsx.ts` via 'index.tsx' |
| 155 | + |
| 156 | + 'jsx/index.jsx': sourcemap.tag` |
| 157 | + import assert from 'assert'; |
| 158 | + export const cjsContext = ${cjsContextCheck}; |
| 159 | + ${declareReact} |
| 160 | + export const jsx = ${jsxCheck}; |
| 161 | + ${preserveName} |
| 162 | + ${sourcemap.test('jsx')} |
| 163 | + `, |
| 164 | + |
| 165 | + 'tsx/index.tsx': sourcemap.tag` |
| 166 | + import assert from 'assert'; |
| 167 | + export const cjsContext = ${cjsContextCheck}; |
| 168 | + ${tsCheck}; |
| 169 | + ${declareReact} |
| 170 | + export const jsx = ${jsxCheck}; |
| 171 | + ${preserveName} |
| 172 | + ${sourcemap.test('tsx')} |
| 173 | + `, |
| 174 | + |
| 175 | + 'mts/index.mts': sourcemap.tag` |
| 176 | + import assert from 'assert'; |
| 177 | + export const mjsHasCjsContext = ${cjsContextCheck}; |
| 178 | + ${tsCheck}; |
| 179 | + ${preserveName} |
| 180 | + ${sourcemap.test('mts')} |
| 181 | + `, |
| 182 | + |
| 183 | + 'cts/index.cts': sourcemap.tag` |
| 184 | + const assert = require('assert'); |
| 185 | + assert(${cjsContextCheck}, 'Should have CJS context'); |
| 186 | + ${tsCheck}; |
| 187 | + ${preserveName} |
| 188 | + ${sourcemap.test('cts')} |
| 189 | + `, |
| 190 | + |
| 191 | + 'expect-errors.js': ` |
| 192 | + export const expectErrors = async (...assertions) => { |
| 193 | + let errors = await Promise.all( |
| 194 | + assertions.map(async ([fn, expectedError]) => { |
| 195 | + let thrown; |
| 196 | + try { |
| 197 | + await fn(); |
| 198 | + } catch (error) { |
| 199 | + thrown = error; |
| 200 | + } |
| 201 | +
|
| 202 | + if (!thrown) { |
| 203 | + return new Error('No error thrown'); |
| 204 | + } else if (!thrown.message.includes(expectedError)) { |
| 205 | + return new Error(\`Message \${JSON.stringify(expectedError)} not found in \${JSON.stringify(thrown.message)}\n\${thrown.stack}\`); |
| 206 | + } |
| 207 | + }), |
| 208 | + ); |
| 209 | +
|
| 210 | + errors = errors.filter(Boolean); |
| 211 | +
|
| 212 | + if (errors.length > 0) { |
| 213 | + console.error(errors); |
| 214 | + process.exitCode = 1; |
| 215 | + } |
| 216 | + }; |
| 217 | + `, |
| 218 | + |
| 219 | + 'file.txt': 'hello', |
| 220 | + |
| 221 | + 'import-typescript-parent.js': sourcemap.tag` |
| 222 | + import './import-typescript-child.js'; |
| 223 | + `, |
| 224 | + |
| 225 | + 'import-typescript-child.ts': sourcemap.tag` |
| 226 | + console.log('imported'); |
| 227 | + `, |
| 228 | + |
| 229 | + 'broken-syntax.ts': 'if', |
| 230 | + |
| 231 | + node_modules: { |
| 232 | + 'pkg-commonjs': { |
| 233 | + 'package.json': JSON.stringify({ |
| 234 | + type: 'commonjs', |
| 235 | + }), |
| 236 | + 'index.js': syntaxLowering, |
| 237 | + }, |
| 238 | + 'pkg-module': { |
| 239 | + 'package.json': JSON.stringify({ |
| 240 | + type: 'module', |
| 241 | + main: './index.js', |
| 242 | + }), |
| 243 | + 'index.js': `${syntaxLowering}\nexport * from "./empty-export"`, |
| 244 | + 'empty-export/index.js': 'export {}', |
| 245 | + }, |
| 246 | + }, |
| 247 | + |
| 248 | + tsconfig: { |
| 249 | + 'file.ts': '', |
| 250 | + |
| 251 | + 'jsx.jsx': ` |
| 252 | + // tsconfig not applied to jsx because allowJs is not set |
| 253 | + import { expectErrors } from '../expect-errors'; |
| 254 | + expectErrors( |
| 255 | + [() => ${jsxCheck}, 'React is not defined'], |
| 256 | +
|
| 257 | + // These should throw unless allowJs is set |
| 258 | + // [() => import ('prefix/file'), "Cannot find package 'prefix'"], |
| 259 | + // [() => import ('paths-exact-match'), "Cannot find package 'paths-exact-match'"], |
| 260 | + // [() => import ('file'), "Cannot find package 'file'"], |
| 261 | + ); |
| 262 | + `, |
| 263 | + |
| 264 | + 'node_modules/tsconfig-should-not-apply': { |
| 265 | + 'package.json': JSON.stringify({ |
| 266 | + exports: { |
| 267 | + import: './index.mjs', |
| 268 | + default: './index.cjs', |
| 269 | + }, |
| 270 | + }), |
| 271 | + 'index.mjs': ` |
| 272 | + import { expectErrors } from '../../../expect-errors'; |
| 273 | + expectErrors( |
| 274 | + [() => import ('prefix/file'), "Cannot find package 'prefix'"], |
| 275 | + [() => import ('paths-exact-match'), "Cannot find package 'paths-exact-match'"], |
| 276 | + [() => import ('file'), "Cannot find package 'file'"], |
| 277 | + ); |
| 278 | + `, |
| 279 | + 'index.cjs': ` |
| 280 | + const { expectErrors } = require('../../../expect-errors'); |
| 281 | + expectErrors( |
| 282 | + [() => require('prefix/file'), "Cannot find module"], |
| 283 | + [() => require('paths-exact-match'), "Cannot find module"], |
| 284 | + [() => require('file'), "Cannot find module"], |
| 285 | + ); |
| 286 | + `, |
| 287 | + }, |
| 288 | + |
| 289 | + 'index.tsx': ` |
| 290 | + ${jsxCheck}; |
| 291 | +
|
| 292 | + import './jsx'; |
| 293 | +
|
| 294 | + // Resolves relative to baseUrl |
| 295 | + import 'file'; |
| 296 | +
|
| 297 | + // Resolves paths - exact match |
| 298 | + import 'paths-exact-match'; |
| 299 | +
|
| 300 | + // Resolves paths - prefix match |
| 301 | + import 'prefix/file'; |
| 302 | +
|
| 303 | + // Resolves paths - suffix match |
| 304 | + import 'file/suffix'; |
| 305 | +
|
| 306 | + // tsconfig should not apply to dependency |
| 307 | + import "tsconfig-should-not-apply"; |
| 308 | + `, |
| 309 | + |
| 310 | + 'tsconfig.json': JSON.stringify({ |
| 311 | + compilerOptions: { |
| 312 | + jsxFactory: 'Array', |
| 313 | + jsxFragmentFactory: 'null', |
| 314 | + baseUrl: '.', |
| 315 | + paths: { |
| 316 | + 'paths-exact-match': ['file'], |
| 317 | + 'prefix/*': ['*'], |
| 318 | + '*/suffix': ['*'], |
| 319 | + }, |
| 320 | + }, |
| 321 | + }), |
| 322 | + |
| 323 | + 'tsconfig-allowJs.json': JSON.stringify({ |
| 324 | + extends: './tsconfig.json', |
| 325 | + compilerOptions: { |
| 326 | + allowJs: true, |
| 327 | + }, |
| 328 | + }), |
| 329 | + }, |
| 330 | +}; |
0 commit comments