babel-plugin-formatjs 10.3.25 → 10.3.26

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 (55) hide show
  1. package/BUILD +90 -0
  2. package/CHANGELOG.md +1139 -0
  3. package/LICENSE.md +0 -0
  4. package/README.md +0 -0
  5. package/global.d.ts +0 -0
  6. package/index.ts +88 -0
  7. package/integration-tests/BUILD +21 -0
  8. package/integration-tests/package.json +5 -0
  9. package/integration-tests/vue/fixtures/App.vue +19 -0
  10. package/integration-tests/vue/fixtures/app.js +6 -0
  11. package/integration-tests/vue/integration.test.ts +62 -0
  12. package/package.json +5 -4
  13. package/tests/__snapshots__/index.test.ts.snap +1246 -0
  14. package/tests/fixtures/2663.js +3 -0
  15. package/tests/fixtures/FormattedMessage.js +14 -0
  16. package/tests/fixtures/additionalComponentNames.js +15 -0
  17. package/tests/fixtures/additionalFunctionNames.js +23 -0
  18. package/tests/fixtures/ast.js +45 -0
  19. package/tests/fixtures/defineMessage.js +57 -0
  20. package/tests/fixtures/defineMessages.js +49 -0
  21. package/tests/fixtures/descriptionsAsObjects.js +18 -0
  22. package/tests/fixtures/empty.js +8 -0
  23. package/tests/fixtures/extractFromFormatMessageCall.js +47 -0
  24. package/tests/fixtures/extractFromFormatMessageCallStateless.js +46 -0
  25. package/tests/fixtures/extractSourceLocation.js +8 -0
  26. package/tests/fixtures/formatMessageCall.js +38 -0
  27. package/tests/fixtures/icuSyntax.js +18 -0
  28. package/tests/fixtures/idInterpolationPattern.js +40 -0
  29. package/tests/fixtures/inline.js +26 -0
  30. package/tests/fixtures/overrideIdFn.js +48 -0
  31. package/tests/fixtures/preserveWhitespace.js +79 -0
  32. package/tests/fixtures/removeDefaultMessage.js +36 -0
  33. package/tests/fixtures/skipExtractionFormattedMessage.js +12 -0
  34. package/tests/fixtures/templateLiteral.js +21 -0
  35. package/tests/index.test.ts +221 -0
  36. package/tsconfig.json +5 -0
  37. package/types.ts +46 -0
  38. package/utils.ts +226 -0
  39. package/visitors/call-expression.ts +208 -0
  40. package/visitors/jsx-opening-element.ts +147 -0
  41. package/index.d.ts +0 -10
  42. package/index.d.ts.map +0 -1
  43. package/index.js +0 -74
  44. package/types.d.ts +0 -31
  45. package/types.d.ts.map +0 -1
  46. package/types.js +0 -2
  47. package/utils.d.ts +0 -34
  48. package/utils.d.ts.map +0 -1
  49. package/utils.js +0 -155
  50. package/visitors/call-expression.d.ts +0 -6
  51. package/visitors/call-expression.d.ts.map +0 -1
  52. package/visitors/call-expression.js +0 -127
  53. package/visitors/jsx-opening-element.d.ts +0 -6
  54. package/visitors/jsx-opening-element.d.ts.map +0 -1
  55. package/visitors/jsx-opening-element.js +0 -89
@@ -0,0 +1,221 @@
1
+ import * as path from 'path'
2
+
3
+ import {transformFileSync} from '@babel/core'
4
+ import plugin from '../'
5
+ import {Options, ExtractedMessageDescriptor} from '../types'
6
+
7
+ function transformAndCheck(fn: string, opts: Options = {}) {
8
+ const filePath = path.join(__dirname, 'fixtures', `${fn}.js`)
9
+ const messages: ExtractedMessageDescriptor[] = []
10
+ const meta = {}
11
+ const {code} = transform(filePath, {
12
+ pragma: '@react-intl',
13
+ ...opts,
14
+ onMsgExtracted(_, msgs) {
15
+ messages.push(...msgs)
16
+ },
17
+ onMetaExtracted(_, m) {
18
+ Object.assign(meta, m)
19
+ },
20
+ })
21
+ expect({
22
+ data: {messages, meta},
23
+ code: code?.trim(),
24
+ }).toMatchSnapshot()
25
+ }
26
+
27
+ test('additionalComponentNames', function () {
28
+ transformAndCheck('additionalComponentNames', {
29
+ additionalComponentNames: ['CustomMessage'],
30
+ })
31
+ })
32
+
33
+ test('additionalFunctionNames', function () {
34
+ transformAndCheck('additionalFunctionNames', {
35
+ additionalFunctionNames: ['t'],
36
+ })
37
+ })
38
+
39
+ test('ast', function () {
40
+ transformAndCheck('ast', {
41
+ ast: true,
42
+ })
43
+ })
44
+
45
+ test('defineMessage', function () {
46
+ transformAndCheck('defineMessage')
47
+ })
48
+
49
+ test('descriptionsAsObjects', function () {
50
+ transformAndCheck('descriptionsAsObjects')
51
+ })
52
+
53
+ test('defineMessages', function () {
54
+ transformAndCheck('defineMessages')
55
+ })
56
+ test('empty', function () {
57
+ transformAndCheck('empty')
58
+ })
59
+ test('extractFromFormatMessageCall', function () {
60
+ transformAndCheck('extractFromFormatMessageCall')
61
+ })
62
+ test('extractFromFormatMessageCallStateless', function () {
63
+ transformAndCheck('extractFromFormatMessageCallStateless')
64
+ })
65
+ test('formatMessageCall', function () {
66
+ transformAndCheck('formatMessageCall')
67
+ })
68
+ test('FormattedMessage', function () {
69
+ transformAndCheck('FormattedMessage')
70
+ })
71
+ test('inline', function () {
72
+ transformAndCheck('inline')
73
+ })
74
+ test('templateLiteral', function () {
75
+ transformAndCheck('templateLiteral')
76
+ })
77
+
78
+ test('idInterpolationPattern', function () {
79
+ transformAndCheck('idInterpolationPattern', {
80
+ idInterpolationPattern: '[folder].[name].[sha512:contenthash:hex:6]',
81
+ })
82
+ })
83
+
84
+ test('idInterpolationPattern default', function () {
85
+ transformAndCheck('idInterpolationPattern')
86
+ })
87
+
88
+ test('GH #2663', function () {
89
+ const filePath = path.join(__dirname, 'fixtures', `2663.js`)
90
+ const messages: ExtractedMessageDescriptor[] = []
91
+ const meta = {}
92
+
93
+ const {code} = transformFileSync(filePath, {
94
+ presets: ['@babel/preset-env', '@babel/preset-react'],
95
+ plugins: [
96
+ [
97
+ plugin,
98
+ {
99
+ pragma: '@react-intl',
100
+ onMsgExtracted(_, msgs) {
101
+ messages.push(...msgs)
102
+ },
103
+ onMetaExtracted(_, m) {
104
+ Object.assign(meta, m)
105
+ },
106
+ } as Options,
107
+ Date.now() + '' + ++cacheBust,
108
+ ],
109
+ ],
110
+ })!
111
+
112
+ expect({
113
+ data: {messages, meta},
114
+ code: code?.trim(),
115
+ }).toMatchSnapshot()
116
+ })
117
+
118
+ test('overrideIdFn', function () {
119
+ transformAndCheck('overrideIdFn', {
120
+ overrideIdFn: (
121
+ id?: string,
122
+ defaultMessage?: string,
123
+ description?: string,
124
+ filePath?: string
125
+ ) => {
126
+ const filename = path.basename(filePath!)
127
+ return `${filename}.${id}.${defaultMessage!.length}.${typeof description}`
128
+ },
129
+ })
130
+ })
131
+ test('removeDefaultMessage', function () {
132
+ transformAndCheck('removeDefaultMessage', {
133
+ removeDefaultMessage: true,
134
+ })
135
+ })
136
+ test('removeDefaultMessage + overrideIdFn', function () {
137
+ transformAndCheck('removeDefaultMessage', {
138
+ removeDefaultMessage: true,
139
+ overrideIdFn: (
140
+ id?: string,
141
+ defaultMessage?: string,
142
+ description?: string,
143
+ filePath?: string
144
+ ) => {
145
+ const filename = path.basename(filePath!)
146
+ return `${filename}.${id}.${defaultMessage!.length}.${typeof description}`
147
+ },
148
+ })
149
+ })
150
+ test('preserveWhitespace', function () {
151
+ transformAndCheck('preserveWhitespace', {
152
+ preserveWhitespace: true,
153
+ })
154
+ })
155
+
156
+ test('extractSourceLocation', function () {
157
+ const filePath = path.join(__dirname, 'fixtures', 'extractSourceLocation.js')
158
+ const messages: ExtractedMessageDescriptor[] = []
159
+ const meta = {}
160
+
161
+ const {code} = transform(filePath, {
162
+ pragma: '@react-intl',
163
+ extractSourceLocation: true,
164
+ onMsgExtracted(_, msgs) {
165
+ messages.push(...msgs)
166
+ },
167
+ onMetaExtracted(_, m) {
168
+ Object.assign(meta, m)
169
+ },
170
+ })
171
+ expect(code?.trim()).toMatchSnapshot()
172
+ expect(messages).toMatchSnapshot([
173
+ {
174
+ file: expect.any(String),
175
+ },
176
+ ])
177
+ expect(meta).toMatchSnapshot()
178
+ })
179
+
180
+ test('Properly throws parse errors', () => {
181
+ expect(() =>
182
+ transform(path.join(__dirname, 'fixtures', 'icuSyntax.js'))
183
+ ).toThrow('SyntaxError: MALFORMED_ARGUMENT')
184
+ })
185
+
186
+ test('skipExtractionFormattedMessage', function () {
187
+ transformAndCheck('skipExtractionFormattedMessage')
188
+ })
189
+
190
+ let cacheBust = 1
191
+
192
+ function transform(
193
+ filePath: string,
194
+ options: Options = {},
195
+ {multiplePasses = false} = {}
196
+ ) {
197
+ function getPluginConfig() {
198
+ return [plugin, options, Date.now() + '' + ++cacheBust]
199
+ }
200
+
201
+ return transformFileSync(filePath, {
202
+ presets: [
203
+ [
204
+ '@babel/preset-env',
205
+ {
206
+ targets: {
207
+ node: '14',
208
+ esmodules: true,
209
+ },
210
+ modules: false,
211
+ useBuiltIns: false,
212
+ ignoreBrowserslistConfig: true,
213
+ },
214
+ ],
215
+ '@babel/preset-react',
216
+ ],
217
+ plugins: multiplePasses
218
+ ? [getPluginConfig(), getPluginConfig()]
219
+ : [getPluginConfig()],
220
+ })!
221
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ // @generated
2
+ {
3
+ // This is purely for IDE, not for compilation
4
+ "extends": "../../tsconfig.json"
5
+ }
package/types.ts ADDED
@@ -0,0 +1,46 @@
1
+ import {NodePath} from '@babel/core'
2
+ import {
3
+ JSXExpressionContainer,
4
+ SourceLocation,
5
+ StringLiteral,
6
+ } from '@babel/types'
7
+
8
+ export interface MessageDescriptor {
9
+ id: string
10
+ defaultMessage?: string
11
+ description?: string
12
+ }
13
+
14
+ export interface State {
15
+ messages: ExtractedMessageDescriptor[]
16
+ meta: Record<string, string>
17
+ componentNames: string[]
18
+ functionNames: string[]
19
+ }
20
+
21
+ export type ExtractedMessageDescriptor = MessageDescriptor &
22
+ Partial<SourceLocation> & {file?: string}
23
+
24
+ export type MessageDescriptorPath = Record<
25
+ keyof MessageDescriptor,
26
+ NodePath<StringLiteral> | NodePath<JSXExpressionContainer> | undefined
27
+ >
28
+
29
+ export interface Options {
30
+ overrideIdFn?: (
31
+ id?: string,
32
+ defaultMessage?: string,
33
+ description?: string,
34
+ filePath?: string
35
+ ) => string
36
+ onMsgExtracted?: (filePath: string, msgs: MessageDescriptor[]) => void
37
+ onMetaExtracted?: (filePath: string, meta: Record<string, string>) => void
38
+ idInterpolationPattern?: string
39
+ removeDefaultMessage?: boolean
40
+ additionalComponentNames?: string[]
41
+ additionalFunctionNames?: string[]
42
+ pragma?: string
43
+ extractSourceLocation?: boolean
44
+ ast?: boolean
45
+ preserveWhitespace?: boolean
46
+ }
package/utils.ts ADDED
@@ -0,0 +1,226 @@
1
+ import * as t from '@babel/types'
2
+ import {parse} from '@formatjs/icu-messageformat-parser'
3
+ import {interpolateName} from '@formatjs/ts-transformer'
4
+
5
+ import {
6
+ Options,
7
+ ExtractedMessageDescriptor,
8
+ MessageDescriptor,
9
+ MessageDescriptorPath,
10
+ } from './types'
11
+ import {NodePath} from '@babel/core'
12
+
13
+ const DESCRIPTOR_PROPS = new Set<keyof MessageDescriptorPath>([
14
+ 'id',
15
+ 'description',
16
+ 'defaultMessage',
17
+ ])
18
+
19
+ function evaluatePath(path: NodePath<any>): string {
20
+ const evaluated = path.evaluate()
21
+ if (evaluated.confident) {
22
+ return evaluated.value
23
+ }
24
+
25
+ throw path.buildCodeFrameError(
26
+ '[React Intl] Messages must be statically evaluate-able for extraction.'
27
+ )
28
+ }
29
+
30
+ export function getMessageDescriptorKey(path: NodePath<any>) {
31
+ if (path.isIdentifier() || path.isJSXIdentifier()) {
32
+ return path.node.name
33
+ }
34
+
35
+ return evaluatePath(path)
36
+ }
37
+
38
+ function getMessageDescriptorValue(
39
+ path?:
40
+ | NodePath<t.StringLiteral>
41
+ | NodePath<t.JSXExpressionContainer>
42
+ | NodePath<t.TemplateLiteral>,
43
+ isMessageNode?: boolean
44
+ ) {
45
+ if (!path) {
46
+ return ''
47
+ }
48
+ if (path.isJSXExpressionContainer()) {
49
+ // If this is already compiled, no need to recompiled it
50
+ if (isMessageNode && path.get('expression').isArrayExpression()) {
51
+ return ''
52
+ }
53
+ path = path.get('expression') as NodePath<t.StringLiteral>
54
+ }
55
+
56
+ // Always trim the Message Descriptor values.
57
+ const descriptorValue = evaluatePath(path)
58
+
59
+ return descriptorValue
60
+ }
61
+
62
+ export function createMessageDescriptor(
63
+ propPaths: [
64
+ NodePath<t.JSXIdentifier> | NodePath<t.Identifier>,
65
+ NodePath<t.StringLiteral> | NodePath<t.JSXExpressionContainer>
66
+ ][]
67
+ ): MessageDescriptorPath {
68
+ return propPaths.reduce(
69
+ (hash: MessageDescriptorPath, [keyPath, valuePath]) => {
70
+ const key = getMessageDescriptorKey(
71
+ keyPath
72
+ ) as keyof MessageDescriptorPath
73
+
74
+ if (DESCRIPTOR_PROPS.has(key)) {
75
+ hash[key] = valuePath
76
+ }
77
+
78
+ return hash
79
+ },
80
+ {
81
+ id: undefined,
82
+ defaultMessage: undefined,
83
+ description: undefined,
84
+ }
85
+ )
86
+ }
87
+
88
+ export function evaluateMessageDescriptor(
89
+ descriptorPath: MessageDescriptorPath,
90
+ isJSXSource = false,
91
+ filename: string | undefined,
92
+ idInterpolationPattern?: string,
93
+ overrideIdFn?: Options['overrideIdFn'],
94
+ preserveWhitespace?: Options['preserveWhitespace']
95
+ ) {
96
+ let id = getMessageDescriptorValue(descriptorPath.id)
97
+ const defaultMessage = getICUMessageValue(
98
+ descriptorPath.defaultMessage,
99
+ {
100
+ isJSXSource,
101
+ },
102
+ preserveWhitespace
103
+ )
104
+ const description = getMessageDescriptorValue(descriptorPath.description)
105
+
106
+ if (overrideIdFn) {
107
+ id = overrideIdFn(id, defaultMessage, description, filename)
108
+ } else if (!id && idInterpolationPattern && defaultMessage) {
109
+ id = interpolateName(
110
+ {resourcePath: filename} as any,
111
+ idInterpolationPattern,
112
+ {
113
+ content: description
114
+ ? `${defaultMessage}#${description}`
115
+ : defaultMessage,
116
+ }
117
+ )
118
+ }
119
+ const descriptor: MessageDescriptor = {
120
+ id,
121
+ }
122
+
123
+ if (description) {
124
+ descriptor.description = description
125
+ }
126
+ if (defaultMessage) {
127
+ descriptor.defaultMessage = defaultMessage
128
+ }
129
+
130
+ return descriptor
131
+ }
132
+
133
+ function getICUMessageValue(
134
+ messagePath?:
135
+ | NodePath<t.StringLiteral>
136
+ | NodePath<t.TemplateLiteral>
137
+ | NodePath<t.JSXExpressionContainer>,
138
+ {isJSXSource = false} = {},
139
+ preserveWhitespace?: Options['preserveWhitespace']
140
+ ) {
141
+ if (!messagePath) {
142
+ return ''
143
+ }
144
+ let message = getMessageDescriptorValue(messagePath, true)
145
+
146
+ if (!preserveWhitespace) {
147
+ message = message.trim().replace(/\s+/gm, ' ')
148
+ }
149
+
150
+ try {
151
+ parse(message)
152
+ } catch (parseError) {
153
+ if (
154
+ isJSXSource &&
155
+ messagePath.isLiteral() &&
156
+ message.indexOf('\\\\') >= 0
157
+ ) {
158
+ throw messagePath.buildCodeFrameError(
159
+ '[React Intl] Message failed to parse. ' +
160
+ 'It looks like `\\`s were used for escaping, ' +
161
+ "this won't work with JSX string literals. " +
162
+ 'Wrap with `{}`. ' +
163
+ 'See: http://facebook.github.io/react/docs/jsx-gotchas.html'
164
+ )
165
+ }
166
+
167
+ throw messagePath.buildCodeFrameError(
168
+ '[React Intl] Message failed to parse. ' +
169
+ 'See: https://formatjs.io/docs/core-concepts/icu-syntax' +
170
+ `\n${parseError}`
171
+ )
172
+ }
173
+ return message
174
+ }
175
+ const EXTRACTED = Symbol('FormatJSExtracted')
176
+ /**
177
+ * Tag a node as extracted
178
+ * Store this in the node itself so that multiple passes work. Specifically
179
+ * if we remove `description` in the 1st pass, 2nd pass will fail since
180
+ * it expect `description` to be there.
181
+ * HACK: We store this in the node instance since this persists across
182
+ * multiple plugin runs
183
+ * @param path
184
+ */
185
+ export function tagAsExtracted(path: NodePath<any>) {
186
+ path.node[EXTRACTED] = true
187
+ }
188
+ /**
189
+ * Check if a node was extracted
190
+ * @param path
191
+ */
192
+ export function wasExtracted(path: NodePath<any>) {
193
+ return !!path.node[EXTRACTED]
194
+ }
195
+
196
+ /**
197
+ * Store a message in our global messages
198
+ * @param messageDescriptor
199
+ * @param path
200
+ * @param opts
201
+ * @param filename
202
+ * @param messages
203
+ */
204
+ export function storeMessage(
205
+ {id, description, defaultMessage}: MessageDescriptor,
206
+ path: NodePath<any>,
207
+ {extractSourceLocation}: Options,
208
+
209
+ filename: string | undefined,
210
+ messages: ExtractedMessageDescriptor[]
211
+ ) {
212
+ if (!id && !defaultMessage) {
213
+ throw path.buildCodeFrameError(
214
+ '[React Intl] Message Descriptors require an `id` or `defaultMessage`.'
215
+ )
216
+ }
217
+
218
+ let loc = {}
219
+ if (extractSourceLocation) {
220
+ loc = {
221
+ file: filename,
222
+ ...path.node.loc,
223
+ }
224
+ }
225
+ messages.push({id, description, defaultMessage, ...loc})
226
+ }
@@ -0,0 +1,208 @@
1
+ import {NodePath, PluginPass} from '@babel/core'
2
+ import * as t from '@babel/types'
3
+ import {Options, State} from '../types'
4
+ import {VisitNodeFunction} from '@babel/traverse'
5
+ import {
6
+ createMessageDescriptor,
7
+ evaluateMessageDescriptor,
8
+ wasExtracted,
9
+ storeMessage,
10
+ tagAsExtracted,
11
+ } from '../utils'
12
+ import {parse} from '@formatjs/icu-messageformat-parser'
13
+
14
+ function assertObjectExpression(
15
+ path: NodePath<any>,
16
+ callee: NodePath<t.Expression | t.V8IntrinsicIdentifier>
17
+ ): asserts path is NodePath<t.ObjectExpression> {
18
+ if (!path || !path.isObjectExpression()) {
19
+ throw path.buildCodeFrameError(
20
+ `[React Intl] \`${
21
+ (callee.get('property') as NodePath<t.Identifier>).node.name
22
+ }()\` must be called with an object expression with values that are React Intl Message Descriptors, also defined as object expressions.`
23
+ )
24
+ }
25
+ }
26
+
27
+ function isFormatMessageCall(
28
+ callee: NodePath<t.Expression | t.V8IntrinsicIdentifier | t.MemberExpression>,
29
+ functionNames: string[]
30
+ ) {
31
+ if (functionNames.find(name => callee.isIdentifier({name}))) {
32
+ return true
33
+ }
34
+
35
+ if (callee.isMemberExpression()) {
36
+ const property = callee.get('property') as NodePath<t.MemberExpression>
37
+ return !!functionNames.find(name => property.isIdentifier({name}))
38
+ }
39
+ return false
40
+ }
41
+
42
+ function getMessagesObjectFromExpression(
43
+ nodePath: NodePath<any>
44
+ ): NodePath<any> {
45
+ let currentPath = nodePath
46
+ while (
47
+ t.isTSAsExpression(currentPath.node) ||
48
+ t.isTSTypeAssertion(currentPath.node) ||
49
+ t.isTypeCastExpression(currentPath.node)
50
+ ) {
51
+ currentPath = currentPath.get('expression') as NodePath<any>
52
+ }
53
+ return currentPath
54
+ }
55
+
56
+ export const visitor: VisitNodeFunction<PluginPass & State, t.CallExpression> =
57
+ function (
58
+ path,
59
+ {
60
+ opts,
61
+ file: {
62
+ opts: {filename},
63
+ },
64
+ }
65
+ ) {
66
+ const {
67
+ overrideIdFn,
68
+ idInterpolationPattern,
69
+ removeDefaultMessage,
70
+ ast,
71
+ preserveWhitespace,
72
+ } = opts as Options
73
+ if (wasExtracted(path)) {
74
+ return
75
+ }
76
+ const {messages, functionNames} = this
77
+ const callee = path.get('callee')
78
+ const args = path.get('arguments')
79
+
80
+ /**
81
+ * Process MessageDescriptor
82
+ * @param messageDescriptor Message Descriptor
83
+ */
84
+ function processMessageObject(
85
+ messageDescriptor: NodePath<t.ObjectExpression>
86
+ ) {
87
+ assertObjectExpression(messageDescriptor, callee)
88
+
89
+ const properties = messageDescriptor.get(
90
+ 'properties'
91
+ ) as NodePath<t.ObjectProperty>[]
92
+
93
+ const descriptorPath = createMessageDescriptor(
94
+ properties.map(
95
+ prop =>
96
+ [prop.get('key'), prop.get('value')] as [
97
+ NodePath<t.Identifier>,
98
+ NodePath<t.StringLiteral>
99
+ ]
100
+ )
101
+ )
102
+
103
+ // If the message is already compiled, don't re-compile it
104
+ if (descriptorPath.defaultMessage?.isArrayExpression()) {
105
+ return
106
+ }
107
+
108
+ // Evaluate the Message Descriptor values, then store it.
109
+ const descriptor = evaluateMessageDescriptor(
110
+ descriptorPath,
111
+ false,
112
+ filename || undefined,
113
+ idInterpolationPattern,
114
+ overrideIdFn,
115
+ preserveWhitespace
116
+ )
117
+ storeMessage(
118
+ descriptor,
119
+ messageDescriptor,
120
+ opts as Options,
121
+ filename || undefined,
122
+ messages
123
+ )
124
+
125
+ const firstProp = properties[0]
126
+ const defaultMessageProp = properties.find(prop => {
127
+ const keyProp = prop.get('key')
128
+ return (
129
+ keyProp.isIdentifier({name: 'defaultMessage'}) ||
130
+ keyProp.isStringLiteral({value: 'defaultMessage'})
131
+ )
132
+ })
133
+ const idProp = properties.find(prop => {
134
+ const keyProp = prop.get('key')
135
+ return (
136
+ keyProp.isIdentifier({name: 'id'}) ||
137
+ keyProp.isStringLiteral({value: 'id'})
138
+ )
139
+ })
140
+
141
+ // Insert ID potentially 1st before removing nodes
142
+ if (idProp) {
143
+ idProp.get('value').replaceWith(t.stringLiteral(descriptor.id))
144
+ } else {
145
+ firstProp.insertBefore(
146
+ t.objectProperty(t.identifier('id'), t.stringLiteral(descriptor.id))
147
+ )
148
+ }
149
+
150
+ // Remove description
151
+ properties
152
+ .find(prop => {
153
+ const keyProp = prop.get('key')
154
+ return (
155
+ keyProp.isIdentifier({name: 'description'}) ||
156
+ keyProp.isStringLiteral({value: 'description'})
157
+ )
158
+ })
159
+ ?.remove()
160
+
161
+ // Pre-parse or remove defaultMessage
162
+ if (defaultMessageProp) {
163
+ if (removeDefaultMessage) {
164
+ defaultMessageProp?.remove()
165
+ } else if (descriptor.defaultMessage) {
166
+ const valueProp = defaultMessageProp.get('value')
167
+ if (ast) {
168
+ valueProp.replaceWithSourceString(
169
+ JSON.stringify(parse(descriptor.defaultMessage))
170
+ )
171
+ } else {
172
+ valueProp.replaceWith(t.stringLiteral(descriptor.defaultMessage))
173
+ }
174
+ }
175
+ }
176
+
177
+ tagAsExtracted(path)
178
+ }
179
+
180
+ // Check that this is `defineMessages` call
181
+ if (
182
+ callee.isIdentifier({name: 'defineMessages'}) ||
183
+ callee.isIdentifier({name: 'defineMessage'})
184
+ ) {
185
+ const firstArgument = args[0]
186
+ const messagesObj = getMessagesObjectFromExpression(firstArgument)
187
+
188
+ assertObjectExpression(messagesObj, callee)
189
+ if (callee.isIdentifier({name: 'defineMessage'})) {
190
+ processMessageObject(messagesObj as NodePath<t.ObjectExpression>)
191
+ } else {
192
+ const properties = messagesObj.get('properties')
193
+ if (Array.isArray(properties)) {
194
+ properties
195
+ .map(prop => prop.get('value') as NodePath<t.ObjectExpression>)
196
+ .forEach(processMessageObject)
197
+ }
198
+ }
199
+ }
200
+
201
+ // Check that this is `intl.formatMessage` call
202
+ if (isFormatMessageCall(callee, functionNames)) {
203
+ const messageDescriptor = args[0]
204
+ if (messageDescriptor.isObjectExpression()) {
205
+ processMessageObject(messageDescriptor)
206
+ }
207
+ }
208
+ }