@formatjs/ts-transformer 3.9.7 → 3.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/BUILD +82 -0
  2. package/CHANGELOG.md +713 -0
  3. package/LICENSE.md +0 -0
  4. package/README.md +0 -0
  5. package/examples/compile.ts +50 -0
  6. package/index.ts +3 -0
  7. package/integration-tests/BUILD +37 -0
  8. package/integration-tests/integration/comp.tsx +40 -0
  9. package/integration-tests/integration/jest.config.js +25 -0
  10. package/integration-tests/integration/ts-jest.test.tsx +32 -0
  11. package/integration-tests/package.json +5 -0
  12. package/integration-tests/vue/fixtures/index.vue +30 -0
  13. package/integration-tests/vue/fixtures/main.ts +4 -0
  14. package/integration-tests/vue/integration.test.ts +75 -0
  15. package/package.json +4 -4
  16. package/src/console_utils.ts +32 -0
  17. package/src/interpolate-name.ts +147 -0
  18. package/src/transform.ts +764 -0
  19. package/src/types.ts +12 -0
  20. package/tests/__snapshots__/index.test.ts.snap +908 -0
  21. package/tests/fixtures/FormattedMessage.tsx +35 -0
  22. package/tests/fixtures/additionalComponentNames.tsx +16 -0
  23. package/tests/fixtures/additionalFunctionNames.tsx +20 -0
  24. package/tests/fixtures/ast.tsx +83 -0
  25. package/tests/fixtures/defineMessages.tsx +67 -0
  26. package/tests/fixtures/defineMessagesPreserveWhitespace.tsx +87 -0
  27. package/tests/fixtures/descriptionsAsObjects.tsx +17 -0
  28. package/tests/fixtures/extractFromFormatMessage.tsx +45 -0
  29. package/tests/fixtures/extractFromFormatMessageStateless.tsx +46 -0
  30. package/tests/fixtures/extractSourceLocation.tsx +8 -0
  31. package/tests/fixtures/formatMessageCall.tsx +44 -0
  32. package/tests/fixtures/inline.tsx +26 -0
  33. package/tests/fixtures/nested.tsx +10 -0
  34. package/tests/fixtures/noImport.tsx +52 -0
  35. package/tests/fixtures/overrideIdFn.tsx +70 -0
  36. package/tests/fixtures/removeDefaultMessage.tsx +22 -0
  37. package/tests/fixtures/removeDescription.tsx +22 -0
  38. package/tests/fixtures/resourcePath.tsx +23 -0
  39. package/tests/fixtures/stringConcat.tsx +26 -0
  40. package/tests/fixtures/templateLiteral.tsx +21 -0
  41. package/tests/index.test.ts +127 -0
  42. package/tests/interpolate-name.test.ts +14 -0
  43. package/ts-jest-integration.ts +9 -0
  44. package/tsconfig.json +5 -0
  45. package/index.d.ts +0 -4
  46. package/index.d.ts.map +0 -1
  47. package/index.js +0 -6
  48. package/src/console_utils.d.ts +0 -4
  49. package/src/console_utils.d.ts.map +0 -1
  50. package/src/console_utils.js +0 -49
  51. package/src/interpolate-name.d.ts +0 -15
  52. package/src/interpolate-name.d.ts.map +0 -1
  53. package/src/interpolate-name.js +0 -95
  54. package/src/transform.d.ts +0 -78
  55. package/src/transform.d.ts.map +0 -1
  56. package/src/transform.js +0 -483
  57. package/src/types.d.ts +0 -12
  58. package/src/types.d.ts.map +0 -1
  59. package/src/types.js +0 -2
  60. package/ts-jest-integration.d.ts +0 -6
  61. package/ts-jest-integration.d.ts.map +0 -1
  62. package/ts-jest-integration.js +0 -10
package/LICENSE.md CHANGED
File without changes
package/README.md CHANGED
File without changes
@@ -0,0 +1,50 @@
1
+ import * as ts from 'typescript'
2
+ import {transform as intlTransformer} from '../'
3
+
4
+ declare module 'fs-extra' {
5
+ export function outputJsonSync(file: string, data: any, opts?: {}): void
6
+ }
7
+ const CJS_CONFIG: ts.CompilerOptions = {
8
+ experimentalDecorators: true,
9
+ jsx: ts.JsxEmit.React,
10
+ module: ts.ModuleKind.CommonJS,
11
+ moduleResolution: ts.ModuleResolutionKind.NodeJs,
12
+ noEmitOnError: false,
13
+ noUnusedLocals: true,
14
+ noUnusedParameters: true,
15
+ stripInternal: true,
16
+ declaration: true,
17
+ baseUrl: __dirname,
18
+ target: ts.ScriptTarget.ES2015,
19
+ }
20
+
21
+ export default function compile(
22
+ input: string,
23
+ compilerOptions: ts.CompilerOptions = CJS_CONFIG
24
+ ) {
25
+ const compilerHost = ts.createCompilerHost(compilerOptions)
26
+ const program = ts.createProgram([input], compilerOptions, compilerHost)
27
+
28
+ const msgs = {}
29
+
30
+ let emitResult = program.emit(undefined, undefined, undefined, undefined, {
31
+ before: [
32
+ intlTransformer({
33
+ overrideIdFn: '[hash:base64:10]',
34
+ }),
35
+ ],
36
+ })
37
+
38
+ let allDiagnostics = ts
39
+ .getPreEmitDiagnostics(program)
40
+ .concat(emitResult.diagnostics)
41
+ console.log(
42
+ ts.formatDiagnosticsWithColorAndContext(allDiagnostics, {
43
+ getCanonicalFileName: fileName => fileName,
44
+ getCurrentDirectory: () => process.cwd(),
45
+ getNewLine: () => ts.sys.newLine,
46
+ })
47
+ )
48
+
49
+ return msgs
50
+ }
package/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './src/transform'
2
+ export * from './src/types'
3
+ export * from './src/interpolate-name'
@@ -0,0 +1,37 @@
1
+ load("@npm//:defs.bzl", "npm_link_all_packages")
2
+ load("//tools:jest.bzl", "jest_test")
3
+
4
+ npm_link_all_packages(name = "node_modules")
5
+
6
+ jest_test(
7
+ name = "integration",
8
+ srcs = glob([
9
+ "integration/*.ts*",
10
+ ]),
11
+ jest_config = "integration/jest.config.js",
12
+ deps = [
13
+ ":node_modules/@formatjs/ts-transformer",
14
+ "//:node_modules/@types/react",
15
+ "//:node_modules/@types/react-dom",
16
+ "//:node_modules/react",
17
+ "//:node_modules/react-dom",
18
+ ],
19
+ )
20
+
21
+ jest_test(
22
+ name = "integration-vue",
23
+ srcs = ["vue/integration.test.ts"] + glob([
24
+ "vue/fixtures/*",
25
+ ]),
26
+ flaky = True,
27
+ deps = [
28
+ ":node_modules/@formatjs/ts-transformer",
29
+ "//:node_modules/@types/node",
30
+ "//:node_modules/@types/webpack",
31
+ "//:node_modules/ts-loader",
32
+ "//:node_modules/vue",
33
+ "//:node_modules/vue-class-component",
34
+ "//:node_modules/vue-loader",
35
+ "//:node_modules/webpack",
36
+ ],
37
+ )
@@ -0,0 +1,40 @@
1
+ import * as React from 'react'
2
+
3
+ const intl = {
4
+ formatMessage: (desc: any) => desc,
5
+ }
6
+
7
+ export function FormattedMessage(props: {
8
+ id?: string
9
+ defaultMessage: string
10
+ description?: string
11
+ children?: any
12
+ }) {
13
+ return (
14
+ <span>
15
+ {props.id} - {JSON.stringify(props.defaultMessage)} - {props.description}{' '}
16
+ - {props.children('fooo')}
17
+ </span>
18
+ )
19
+ }
20
+
21
+ function defineMessage(any: any): any {
22
+ return any
23
+ }
24
+ export const msg = defineMessage({
25
+ defaultMessage: 'defineMessage',
26
+ description: 'foo',
27
+ })
28
+
29
+ export const Component = (): React.ReactElement => {
30
+ return (
31
+ <FormattedMessage defaultMessage="test message">
32
+ {(chunks: any) => chunks}
33
+ </FormattedMessage>
34
+ )
35
+ }
36
+
37
+ export const formattedMessage = intl.formatMessage({
38
+ defaultMessage: 'formatMessage',
39
+ description: 'foo',
40
+ })
@@ -0,0 +1,25 @@
1
+ console.log(process.cwd())
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'jsdom',
5
+ globals: {
6
+ 'ts-jest': {
7
+ tsconfig: 'tsconfig.json',
8
+ astTransformers: {
9
+ before: [
10
+ {
11
+ path: require.resolve(
12
+ '@formatjs/ts-transformer/ts-jest-integration'
13
+ ),
14
+ options: {
15
+ // options
16
+ overrideIdFn: '[sha512:contenthash:base64:6]',
17
+ ast: true,
18
+ },
19
+ },
20
+ ],
21
+ },
22
+ },
23
+ },
24
+ verbose: true,
25
+ }
@@ -0,0 +1,32 @@
1
+ import {msg, Component, formattedMessage} from './comp'
2
+ import * as React from 'react'
3
+ import * as ReactDOMServer from 'react-dom/server'
4
+ describe('ts-jest transformer', function () {
5
+ it('should work with ts-jest', function () {
6
+ expect(msg).toEqual({
7
+ defaultMessage: [
8
+ {
9
+ type: 0,
10
+ value: 'defineMessage',
11
+ },
12
+ ],
13
+ id: 'Vg+BA7',
14
+ })
15
+ })
16
+ it('intl.formatMessage', function () {
17
+ expect(formattedMessage).toEqual({
18
+ defaultMessage: [
19
+ {
20
+ type: 0,
21
+ value: 'formatMessage',
22
+ },
23
+ ],
24
+ id: 'w8L3pO',
25
+ })
26
+ })
27
+ it('Comp', function () {
28
+ expect(ReactDOMServer.renderToString(<Component />)).toEqual(
29
+ '<span data-reactroot="">lQsqfv<!-- --> - <!-- -->[{&quot;type&quot;:0,&quot;value&quot;:&quot;test message&quot;}]<!-- --> - <!-- --> <!-- -->- <!-- -->fooo</span>'
30
+ )
31
+ })
32
+ })
@@ -0,0 +1,5 @@
1
+ {
2
+ "devDependencies": {
3
+ "@formatjs/ts-transformer": "workspace:*"
4
+ }
5
+ }
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <p>
3
+ Index
4
+ {{ $formatMessage({defaultMessage: 'test message (id not injected)'}) }}
5
+ </p>
6
+ </template>
7
+
8
+ <script lang="ts">
9
+ import {inject} from 'vue'
10
+ import {Vue} from 'vue-class-component'
11
+
12
+ class Index extends Vue {
13
+ public created() {
14
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
15
+ const intl = inject('intl')!
16
+
17
+ console.log(
18
+ // @ts-ignore
19
+ intl.formatMessage({defaultMessage: 'message in created (id injected)'})
20
+ )
21
+ }
22
+ }
23
+
24
+ export default Index
25
+ </script>
26
+
27
+ <script lang="ts" setup>
28
+ const intl = useIntl()
29
+ console.log(intl.formatMessage({defaultMessage: 'script setup'}))
30
+ </script>
@@ -0,0 +1,4 @@
1
+ import {createApp} from 'vue'
2
+ import App from './index.vue'
3
+
4
+ createApp(App).mount('#app')
@@ -0,0 +1,75 @@
1
+ import webpack from 'webpack'
2
+ import {VueLoaderPlugin} from 'vue-loader'
3
+ import {readFileSync} from 'fs'
4
+ import {join} from 'path'
5
+ import {transform} from '@formatjs/ts-transformer'
6
+ test('tranpilation', function (done) {
7
+ webpack(
8
+ {
9
+ entry: require.resolve('./fixtures/main.ts'),
10
+ output: {
11
+ path: __dirname,
12
+ filename: 'out.js',
13
+ },
14
+ module: {
15
+ rules: [
16
+ {
17
+ test: /\.vue$/,
18
+ loader: 'vue-loader',
19
+ },
20
+ {
21
+ test: /\.[t|j]s$/u,
22
+ use: [
23
+ {
24
+ loader: 'ts-loader',
25
+ options: {
26
+ appendTsSuffixTo: [/\.vue$/u],
27
+ getCustomTransformers() {
28
+ return {
29
+ before: [
30
+ transform({
31
+ overrideIdFn: '[sha512:contenthash:base64:6]',
32
+ }),
33
+ ],
34
+ }
35
+ },
36
+ transpileOnly: true,
37
+ },
38
+ },
39
+ ],
40
+ },
41
+ ],
42
+ },
43
+ plugins: [new VueLoaderPlugin()],
44
+ },
45
+ (err, stats) => {
46
+ expect(err).toBeNull()
47
+ const statsJson = stats?.toJson()
48
+ if (!statsJson) {
49
+ throw new Error('missing stats')
50
+ }
51
+ if (err) {
52
+ throw err
53
+ }
54
+ if (stats?.hasErrors()) {
55
+ console.error(statsJson.errors)
56
+ throw new Error('err compiling')
57
+ }
58
+ const outFile = join(
59
+ statsJson.outputPath || __dirname,
60
+ statsJson.assets?.[0].name || 'out.js'
61
+ )
62
+ const outFileContent = readFileSync(outFile, 'utf-8')
63
+ expect(outFileContent).toContain(
64
+ '.formatMessage({id:"XOeJ9m",defaultMessage:"message in created (id injected)"}))}'
65
+ )
66
+ expect(outFileContent).toContain(
67
+ '$formatMessage({id:"GuoEHM",defaultMessage:"test message (id not injected)"}))'
68
+ )
69
+ expect(outFileContent).toContain(
70
+ '.formatMessage({id:"S3wEt4",defaultMessage:"script setup"}))'
71
+ )
72
+ done()
73
+ }
74
+ )
75
+ }, 30000)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formatjs/ts-transformer",
3
- "version": "3.9.7",
3
+ "version": "3.9.10",
4
4
  "description": "TS Compiler transformer for formatjs",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "react-intl"
18
18
  ],
19
19
  "dependencies": {
20
- "@formatjs/icu-messageformat-parser": "2.1.2",
20
+ "@formatjs/icu-messageformat-parser": "2.1.5",
21
21
  "@types/json-stable-stringify": "^1.0.32",
22
22
  "@types/node": "14 || 16 || 17",
23
23
  "chalk": "^4.0.0",
@@ -26,7 +26,7 @@
26
26
  "typescript": "^4.5"
27
27
  },
28
28
  "peerDependencies": {
29
- "ts-jest": "27"
29
+ "ts-jest": "27 || 28"
30
30
  },
31
31
  "peerDependenciesMeta": {
32
32
  "ts-jest": {
@@ -39,4 +39,4 @@
39
39
  "url": "https://github.com/formatjs/formatjs/issues"
40
40
  },
41
41
  "homepage": "https://github.com/formatjs/formatjs#readme"
42
- }
42
+ }
@@ -0,0 +1,32 @@
1
+ import {green, red, yellow} from 'chalk'
2
+ import {format} from 'util'
3
+
4
+ const LEVEL_COLORS = {
5
+ debug: green,
6
+ warn: yellow,
7
+ error: red,
8
+ }
9
+
10
+ function label(level: keyof typeof LEVEL_COLORS, message: string) {
11
+ return `[@formatjs/ts-transformer] [${LEVEL_COLORS[level](
12
+ level.toUpperCase()
13
+ )}] ${message}`
14
+ }
15
+
16
+ export async function debug(message: string, ...args: any[]) {
17
+ if (process.env.LOG_LEVEL !== 'debug') {
18
+ return
19
+ }
20
+ console.error(format(label('debug', message), ...args))
21
+ console.error('\n')
22
+ }
23
+
24
+ export function warn(message: string, ...args: any[]): void {
25
+ console.error(format(label('warn', message), ...args))
26
+ console.error('\n')
27
+ }
28
+
29
+ export function error(message: string, ...args: any[]): void {
30
+ console.error(format(label('error', message), ...args))
31
+ console.error('\n')
32
+ }
@@ -0,0 +1,147 @@
1
+ import * as path from 'path'
2
+ import {BinaryToTextEncoding, createHash} from 'crypto'
3
+ export interface LoaderContext {
4
+ resourceQuery?: string
5
+ resourcePath?: string
6
+ options?: {
7
+ customInterpolateName(
8
+ this: LoaderContext,
9
+ url: string,
10
+ name: string | NameFn,
11
+ options: Options
12
+ ): string
13
+ }
14
+ }
15
+
16
+ export interface Options {
17
+ context?: string
18
+ content?: string
19
+ regExp?: RegExp
20
+ }
21
+
22
+ export type NameFn = (resourcePath?: string, resourceQuery?: string) => string
23
+
24
+ function getHashDigest(
25
+ content: string,
26
+ hashType = 'md5',
27
+ digestType: BinaryToTextEncoding = 'hex',
28
+ length = 9999
29
+ ) {
30
+ const hasher = createHash(hashType)
31
+ hasher.update(content)
32
+ return hasher.digest(digestType).slice(0, length)
33
+ }
34
+
35
+ export function interpolateName(
36
+ loaderContext: LoaderContext,
37
+ name: string | NameFn,
38
+ options: Options
39
+ ) {
40
+ let filename
41
+
42
+ const hasQuery =
43
+ loaderContext.resourceQuery && loaderContext.resourceQuery.length > 1
44
+
45
+ if (typeof name === 'function') {
46
+ filename = name(
47
+ loaderContext.resourcePath,
48
+ hasQuery ? loaderContext.resourceQuery : undefined
49
+ )
50
+ } else {
51
+ filename = name || '[hash].[ext]'
52
+ }
53
+
54
+ const context = options.context
55
+ const content = options.content
56
+ const regExp = options.regExp
57
+
58
+ let ext = 'bin'
59
+ let basename = 'file'
60
+ let directory = ''
61
+ let folder = ''
62
+ let query = ''
63
+
64
+ if (loaderContext.resourcePath) {
65
+ const parsed = path.parse(loaderContext.resourcePath)
66
+ let resourcePath = loaderContext.resourcePath
67
+
68
+ if (parsed.ext) {
69
+ ext = parsed.ext.slice(1)
70
+ }
71
+
72
+ if (parsed.dir) {
73
+ basename = parsed.name
74
+ resourcePath = parsed.dir + path.sep
75
+ }
76
+
77
+ if (typeof context !== 'undefined') {
78
+ directory = path
79
+ .relative(context, resourcePath + '_')
80
+ .replace(/\\/g, '/')
81
+ .replace(/\.\.(\/)?/g, '_$1')
82
+ directory = directory.slice(0, -1)
83
+ } else {
84
+ directory = resourcePath.replace(/\\/g, '/').replace(/\.\.(\/)?/g, '_$1')
85
+ }
86
+
87
+ if (directory.length === 1) {
88
+ directory = ''
89
+ } else if (directory.length > 1) {
90
+ folder = path.basename(directory)
91
+ }
92
+ }
93
+
94
+ if (loaderContext.resourceQuery && loaderContext.resourceQuery.length > 1) {
95
+ query = loaderContext.resourceQuery
96
+
97
+ const hashIdx = query.indexOf('#')
98
+
99
+ if (hashIdx >= 0) {
100
+ query = query.slice(0, hashIdx)
101
+ }
102
+ }
103
+
104
+ let url = filename
105
+
106
+ if (content) {
107
+ // Match hash template
108
+ url = url
109
+ // `hash` and `contenthash` are same in `loader-utils` context
110
+ // let's keep `hash` for backward compatibility
111
+ .replace(
112
+ /\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi,
113
+ (_, hashType, digestType, maxLength) =>
114
+ getHashDigest(content, hashType, digestType, parseInt(maxLength, 10))
115
+ )
116
+ }
117
+
118
+ url = url
119
+ .replace(/\[ext\]/gi, () => ext)
120
+ .replace(/\[name\]/gi, () => basename)
121
+ .replace(/\[path\]/gi, () => directory)
122
+ .replace(/\[folder\]/gi, () => folder)
123
+ .replace(/\[query\]/gi, () => query)
124
+
125
+ if (regExp && loaderContext.resourcePath) {
126
+ const match = loaderContext.resourcePath.match(new RegExp(regExp))
127
+
128
+ match &&
129
+ match.forEach((matched, i) => {
130
+ url = url.replace(new RegExp('\\[' + i + '\\]', 'ig'), matched)
131
+ })
132
+ }
133
+
134
+ if (
135
+ typeof loaderContext.options === 'object' &&
136
+ typeof loaderContext.options.customInterpolateName === 'function'
137
+ ) {
138
+ url = loaderContext.options.customInterpolateName.call(
139
+ loaderContext,
140
+ url,
141
+ name,
142
+ options
143
+ )
144
+ }
145
+
146
+ return url
147
+ }