UNPKG

4.05 kBJavaScriptView Raw
1const { dirname } = require('path');
2const readPkgUp = require('read-pkg-up');
3
4const DEFINE_MESSAGES = 'defineMessages';
5
6const COMPONENT_NAMES = ['FormattedMessage', 'FormattedHTMLMessage'];
7
8function getPrefix(state) {
9 let { prefix } = state.opts;
10 if (prefix && !prefix.endsWith(':')) prefix = `${prefix}:`;
11 return prefix;
12}
13
14function referencesImport(path) {
15 if (!(path.isIdentifier() || path.isJSXIdentifier())) return false;
16 return COMPONENT_NAMES.some(name =>
17 path.referencesImport('react-intl', name),
18 );
19}
20
21const PREFIXES = new Map();
22
23const PREFIX = Symbol('namespace prefix');
24
25function getPrefixFromPackage(filename) {
26 for (const [root, prefix] of PREFIXES.entries()) {
27 if (filename.startsWith(root)) {
28 return prefix;
29 }
30 }
31
32 const pkgUpResult = readPkgUp.sync({ cwd: dirname(filename) });
33 if (!pkgUpResult) return '';
34
35 const prefix = `${pkgUpResult.packageJson.name}:`;
36 PREFIXES.set(dirname(pkgUpResult.path), prefix);
37 return prefix;
38}
39
40function getMessagesObjectFromExpression(nodePath) {
41 let currentPath = nodePath;
42 while (
43 currentPath.isTSAsExpression() ||
44 currentPath.isTSTypeAssertion() ||
45 currentPath.isTypeCastExpression()
46 ) {
47 currentPath = currentPath.get('expression');
48 }
49 return currentPath;
50}
51
52function isFormatMessageCall(callee) {
53 if (!callee.isMemberExpression()) {
54 return false;
55 }
56 const object = callee.get('object');
57 const property = callee.get('property');
58
59 return (
60 property.isIdentifier() &&
61 property.node.name === 'formatMessage' &&
62 // things like `intl.formatMessage`
63 ((object.isIdentifier() && object.node.name === 'intl') ||
64 // things like `this.props.intl.formatMessage`
65 (object.isMemberExpression() &&
66 object.get('property').node.name === 'intl'))
67 );
68}
69
70module.exports = function namespacePlugin({ types: t }) {
71 return {
72 pre(file) {
73 const prefix =
74 getPrefix(this) || getPrefixFromPackage(file.opts.filename);
75
76 file.set(PREFIX, prefix);
77 },
78 visitor: {
79 JSXOpeningElement(path, state) {
80 const name = path.get('name');
81 if (!referencesImport(name)) return;
82
83 const prefix = state.file.get(PREFIX);
84
85 const idAttr = path
86 .get('attributes')
87 .find(attr => attr.isJSXAttribute() && attr.node.name.name === 'id');
88
89 if (idAttr && !idAttr.node.value.value.startsWith(prefix)) {
90 idAttr
91 .get('value')
92 .replaceWith(
93 t.StringLiteral(`${prefix}${idAttr.node.value.value}`),
94 );
95 }
96 },
97
98 CallExpression(path, state) {
99 const prefix = state.file.get(PREFIX);
100 const callee = path.get('callee');
101
102 function processMessageObject(messageObj) {
103 if (!messageObj || !messageObj.isObjectExpression()) {
104 return;
105 }
106
107 const idProp = messageObj.get('properties').find(p => {
108 // this may be a Literal or StringLiteral depending
109 // on if the key is quoted or not
110 const keyNode = p.get('key').node;
111 return keyNode.name === 'id' || keyNode.value === 'id';
112 });
113
114 const value = idProp && idProp.get('value');
115
116 if (value && !value.node.value.startsWith(prefix)) {
117 value.replaceWith(
118 t.StringLiteral(`${prefix}${value.node.value}`), // eslint-disable-line new-cap
119 );
120 }
121 }
122
123 if (callee.isIdentifier() && callee.node.name === DEFINE_MESSAGES) {
124 getMessagesObjectFromExpression(path.get('arguments')[0])
125 .get('properties')
126 .map(prop => prop.get('value'))
127 .forEach(processMessageObject);
128 } else if (isFormatMessageCall(callee)) {
129 const messageDescriptor = getMessagesObjectFromExpression(
130 path.get('arguments')[0],
131 );
132
133 if (messageDescriptor.isObjectExpression()) {
134 processMessageObject(messageDescriptor);
135 }
136 }
137 },
138 },
139 };
140};