@lwc/sfdc-compiler-utils 2.19.0
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.
- package/LICENSE.txt +21 -0
- package/dist/__tests__/namespace.spec.d.ts +1 -0
- package/dist/__tests__/namespace.spec.js +462 -0
- package/dist/__tests__/namespace.spec.js.map +1 -0
- package/dist/__tests__/no-explicit-namespace.spec.d.ts +1 -0
- package/dist/__tests__/no-explicit-namespace.spec.js +258 -0
- package/dist/__tests__/no-explicit-namespace.spec.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace-utils.d.ts +23 -0
- package/dist/namespace-utils.js +290 -0
- package/dist/namespace-utils.js.map +1 -0
- package/jest.config.js +6 -0
- package/package.json +15 -0
- package/src/__tests__/namespace.spec.ts +760 -0
- package/src/__tests__/no-explicit-namespace.spec.ts +350 -0
- package/src/index.ts +9 -0
- package/src/namespace-utils.ts +366 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// Copyright (c) 2022, Salesforce, Inc.,
|
|
2
|
+
// All rights reserved.
|
|
3
|
+
// For full license text, see the LICENSE.txt file
|
|
4
|
+
|
|
5
|
+
const SALESFORCE_IMPORT_PREFIX = '@salesforce/';
|
|
6
|
+
const DEFAULT_SALESFORCE_NAMESPACE = 'c';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param: moduleName - 'c.label'
|
|
10
|
+
* @param: moduleType - 'label'
|
|
11
|
+
* @param: namespaceMapping<sting, string> - { c: 'ns' }
|
|
12
|
+
*
|
|
13
|
+
* @return: string - 'label/ns.label'
|
|
14
|
+
*/
|
|
15
|
+
export function getNamespacedIdForType(
|
|
16
|
+
moduleName: string,
|
|
17
|
+
moduleType: string | undefined,
|
|
18
|
+
namespaceMapping: { [name: string]: string }
|
|
19
|
+
): string | undefined {
|
|
20
|
+
validateParameters(moduleName, namespaceMapping);
|
|
21
|
+
|
|
22
|
+
let updatedModuleName;
|
|
23
|
+
|
|
24
|
+
if (moduleType && moduleType !== 'module') {
|
|
25
|
+
updatedModuleName = getSalesforceNamespacedModule(moduleName, moduleType, namespaceMapping);
|
|
26
|
+
} else {
|
|
27
|
+
updatedModuleName = getStandardNamespacedModule(moduleName, namespaceMapping);
|
|
28
|
+
}
|
|
29
|
+
return updatedModuleName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve and alias @salesforce scoped resource id. If provided resource doesn't start
|
|
34
|
+
* with @salesforce prefix, the resource is treated as a regular module
|
|
35
|
+
*
|
|
36
|
+
* @param: moduleName - '@salesforce/label/c.label'
|
|
37
|
+
* @param: namespaceMapping<sting, string> - { c: 'ns' }
|
|
38
|
+
*
|
|
39
|
+
* @return: string - 'ns.label'
|
|
40
|
+
*/
|
|
41
|
+
export function getNamespacedIdForResource(
|
|
42
|
+
moduleName: string,
|
|
43
|
+
namespaceMapping: { [name: string]: string }
|
|
44
|
+
): undefined | string {
|
|
45
|
+
validateParameters(moduleName, namespaceMapping);
|
|
46
|
+
|
|
47
|
+
let updatedModuleName;
|
|
48
|
+
if (moduleName.startsWith(SALESFORCE_IMPORT_PREFIX)) {
|
|
49
|
+
const [prefix, type, value] = moduleName.split('/');
|
|
50
|
+
const updatedModuleId = getSalesforceNamespacedModule(value, type, namespaceMapping);
|
|
51
|
+
if (updatedModuleId) {
|
|
52
|
+
updatedModuleName = [prefix, type, updatedModuleId].join('/');
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
const updateModuleId = getStandardNamespacedModule(moduleName, namespaceMapping);
|
|
56
|
+
if (updateModuleId) {
|
|
57
|
+
updatedModuleName = updateModuleId;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return updatedModuleName;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getSalesforceNamespacedModule(
|
|
65
|
+
value: string,
|
|
66
|
+
type: string,
|
|
67
|
+
namespaceMapping: { [name: string]: string }
|
|
68
|
+
): undefined | string {
|
|
69
|
+
if (!value) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let existingNamespace: string | undefined;
|
|
74
|
+
let newNamespace: string | undefined;
|
|
75
|
+
let parts;
|
|
76
|
+
|
|
77
|
+
const defaultTargetValue = namespaceMapping[DEFAULT_SALESFORCE_NAMESPACE];
|
|
78
|
+
|
|
79
|
+
switch (type) {
|
|
80
|
+
// @salesforce/label/c.label1 -> @salesforce/label/namespace.label1
|
|
81
|
+
case 'label':
|
|
82
|
+
parts = value.split('.');
|
|
83
|
+
|
|
84
|
+
// filter out invalid format:
|
|
85
|
+
// @salesforce/label/c..label
|
|
86
|
+
if (parts.some((p) => !p)) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
existingNamespace = parts[0];
|
|
91
|
+
|
|
92
|
+
// no namespace - @salesforce/label/label1
|
|
93
|
+
if (parts.length === 1) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
newNamespace = namespaceMapping[existingNamespace];
|
|
97
|
+
|
|
98
|
+
return newNamespace
|
|
99
|
+
? value.replace(`${existingNamespace}.`, `${newNamespace}.`)
|
|
100
|
+
: value;
|
|
101
|
+
|
|
102
|
+
// @salesforce/resourceUrl/resource1 -> @salesforce/resourceUrl/namespace__resource1
|
|
103
|
+
// @salesforce/contentAssetUrl/resource1 -> @salesforce/contentAssetUrl/namespace__resource1
|
|
104
|
+
// @salesforce/customPermission/resource1 -> @salesforce/customPermission/namespace__resource1
|
|
105
|
+
// @salesforce/komaci/resource1 -> @salesforce/komaci/namespace__resource1
|
|
106
|
+
case 'resourceUrl':
|
|
107
|
+
case 'contentAssetUrl':
|
|
108
|
+
case 'customPermission':
|
|
109
|
+
case 'komaci':
|
|
110
|
+
parts = value.split('__');
|
|
111
|
+
|
|
112
|
+
// filter out invalid format:
|
|
113
|
+
if (parts.some((p) => !p)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (parts.length === 1) {
|
|
118
|
+
return defaultTargetValue && `${defaultTargetValue}__${value}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
existingNamespace = parts[0];
|
|
122
|
+
|
|
123
|
+
// @salesforce/resourceUrl/namespace__resource1
|
|
124
|
+
newNamespace = namespaceMapping[existingNamespace];
|
|
125
|
+
|
|
126
|
+
return newNamespace
|
|
127
|
+
? value.replace(`${existingNamespace}__`, `${newNamespace}__`)
|
|
128
|
+
: value;
|
|
129
|
+
|
|
130
|
+
// Standard messageChannels should have a namespace, e.g. @salesforce/messageChannel/standardNs_messageChannel1
|
|
131
|
+
// Undefined behavior for a missing "__c" suffix (indicating custom) and a missing namespace
|
|
132
|
+
// @salesforce/messageChannel/messageChannel1 -> undefined
|
|
133
|
+
// @salesforce/messageChannel/messageChannel1__c -> @salesforce/messageChannel/namespace__messageChannel1__c
|
|
134
|
+
case 'messageChannel':
|
|
135
|
+
parts = value.split('__');
|
|
136
|
+
|
|
137
|
+
// filter out invalid format: @salesforce/messageChannel/messageChannel1
|
|
138
|
+
if (parts.length === 1 || parts.some((p) => !p)) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (value.endsWith('__c') && parts.length === 2) {
|
|
143
|
+
return defaultTargetValue && `${defaultTargetValue}__${value}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
existingNamespace = parts[0];
|
|
147
|
+
|
|
148
|
+
// @salesforce/messageChannel/namespace__messageChannel1
|
|
149
|
+
newNamespace = namespaceMapping[existingNamespace];
|
|
150
|
+
|
|
151
|
+
return newNamespace
|
|
152
|
+
? value.replace(`${existingNamespace}__`, `${newNamespace}__`)
|
|
153
|
+
: value;
|
|
154
|
+
// apexContinuation is the same as apex
|
|
155
|
+
// @salesforce/apexContinuation/MyClass.methodA -> @salesforce/apexContinuation/acme.MyClass.methodA
|
|
156
|
+
// @salesforce/apex/MyClass.methodA -> @salesforce/apex/acme.MyClass.methodA
|
|
157
|
+
case 'apexContinuation':
|
|
158
|
+
case 'apex':
|
|
159
|
+
// We detect if the apex reference already contains the namespace by checking the number of parts in its
|
|
160
|
+
// reference. If the namespace is present the value would have the following from <ns>.<class>.<method>
|
|
161
|
+
// otherwise it would be <class>.<method>.
|
|
162
|
+
|
|
163
|
+
parts = value.split('.');
|
|
164
|
+
|
|
165
|
+
// filter out invalid format:
|
|
166
|
+
// @salesforce/apex/MyClass
|
|
167
|
+
// @salesforce/apex/MyClass..methodA
|
|
168
|
+
if (parts.length === 1 || parts.some((p) => !p)) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// @salesforce/apex/MyClass.method -> missing namespace
|
|
173
|
+
if (parts.length === 2) {
|
|
174
|
+
return defaultTargetValue && `${defaultTargetValue}.${value}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
existingNamespace = parts[0];
|
|
178
|
+
newNamespace = namespaceMapping[existingNamespace];
|
|
179
|
+
return newNamespace
|
|
180
|
+
? value.replace(`${existingNamespace}.`, `${newNamespace}.`)
|
|
181
|
+
: value;
|
|
182
|
+
|
|
183
|
+
// @salesforce/schema/CustomObject1__c -> @salesforce/schema/acme__CustomObject1__c
|
|
184
|
+
// @salesforce/schema/Account.CustomField__c -> @salesforce/schema/Account.acme__CustomField__c
|
|
185
|
+
// @salesforce/schema/Account.Relationship__r.CustomField__c ->
|
|
186
|
+
// -> @salesforce/schema/Account.acme__Relationship__r.acme__CustomField__c
|
|
187
|
+
case 'schema': {
|
|
188
|
+
// For each object, field and relationship that is part of the module name, if it is custom and has no
|
|
189
|
+
// namespace, we need to add the target namespace.
|
|
190
|
+
const schemaParts = value.split('.');
|
|
191
|
+
const remappedParts = schemaParts.map((part) => {
|
|
192
|
+
const isCustom = part.endsWith('__c') || part.endsWith('__r');
|
|
193
|
+
if (!isCustom) {
|
|
194
|
+
return part;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
parts = part.split('__');
|
|
198
|
+
|
|
199
|
+
// @salesforce/schema/c____CustomObject1
|
|
200
|
+
if (parts.some((p) => !p)) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const isNamespaced = parts.length > 2;
|
|
205
|
+
|
|
206
|
+
// @salesforce/schema/CustomObject1__c
|
|
207
|
+
if (!isNamespaced) {
|
|
208
|
+
return defaultTargetValue && `${defaultTargetValue}__${part}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// @salesforce/schema/acme__CustomObject1__c
|
|
212
|
+
existingNamespace = parts[0];
|
|
213
|
+
newNamespace = namespaceMapping[existingNamespace];
|
|
214
|
+
|
|
215
|
+
// replace only if new mapping exists. ex: acme -> newAcme
|
|
216
|
+
return newNamespace
|
|
217
|
+
? part.replace(`${existingNamespace}__`, `${newNamespace}__`)
|
|
218
|
+
: part;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// return undefined if any of the parts were not successfully remapped
|
|
222
|
+
return remappedParts.some((p) => !p) ? undefined : remappedParts.join('.');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// noop
|
|
226
|
+
case 'accessCheck':
|
|
227
|
+
case 'client':
|
|
228
|
+
case 'commerce':
|
|
229
|
+
case 'community':
|
|
230
|
+
case 'cssvars':
|
|
231
|
+
case 'gate':
|
|
232
|
+
case 'i18n':
|
|
233
|
+
case 'internal':
|
|
234
|
+
case 'metric':
|
|
235
|
+
case 'site':
|
|
236
|
+
case 'slds':
|
|
237
|
+
case 'user':
|
|
238
|
+
case 'userPermission':
|
|
239
|
+
return undefined;
|
|
240
|
+
default:
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Failed to apply namespace mapping to "${value}" due to unknown @salesforce type ${type}.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getStandardNamespacedModule(
|
|
248
|
+
moduleName: string,
|
|
249
|
+
namespaceMapping: { [name: string]: string }
|
|
250
|
+
): undefined | string {
|
|
251
|
+
// module format expected to be in the following format: c/utils
|
|
252
|
+
const parts = moduleName.split('/');
|
|
253
|
+
|
|
254
|
+
// assert no empty values
|
|
255
|
+
if (parts.some((p) => !p)) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const original = parts[0];
|
|
260
|
+
|
|
261
|
+
const target = namespaceMapping[original];
|
|
262
|
+
return target ? moduleName.replace(`${original}/`, `${target}/`) : undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function validateParameters(
|
|
266
|
+
moduleName: string,
|
|
267
|
+
namespaceMapping: { [name: string]: string }
|
|
268
|
+
): void {
|
|
269
|
+
if (!moduleName) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Failed to apply namespace mapping to "${moduleName}". Missing required "moduleName" parameter`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!namespaceMapping) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
`Failed to apply namespace mapping to "${moduleName}". Missing required "namespaceMapping" parameter`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function containsExplicitNamespace(
|
|
283
|
+
value: string,
|
|
284
|
+
type: string,
|
|
285
|
+
explicitNamespace: string
|
|
286
|
+
): boolean {
|
|
287
|
+
if (!value || !type || !explicitNamespace) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Failed to check explicit namespace usage.` +
|
|
290
|
+
`Expected string values for "value", "type", and "explicitNamespace" parameters, ` +
|
|
291
|
+
`received "${value}," "${type}", "${explicitNamespace}".`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// nothing to match when default namespace is supplied
|
|
296
|
+
if (explicitNamespace === DEFAULT_SALESFORCE_NAMESPACE) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let isExplicitNamespaceUsed = false;
|
|
301
|
+
|
|
302
|
+
switch (type) {
|
|
303
|
+
case 'component':
|
|
304
|
+
// lowercase incoming namespace value because template references are always lowercased
|
|
305
|
+
isExplicitNamespaceUsed = value.startsWith(`${explicitNamespace.toLocaleLowerCase()}-`);
|
|
306
|
+
break;
|
|
307
|
+
|
|
308
|
+
case 'module':
|
|
309
|
+
isExplicitNamespaceUsed = value.startsWith(`${explicitNamespace}/`);
|
|
310
|
+
break;
|
|
311
|
+
|
|
312
|
+
// @salesforce/label/c.label1 -> @salesforce/label/namespace.label1
|
|
313
|
+
case 'label':
|
|
314
|
+
isExplicitNamespaceUsed = value.startsWith(`${explicitNamespace}.`);
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
// @salesforce/schema/Account.CustomField__c -> @salesforce/schema/Account.acme__CustomField__c
|
|
318
|
+
// @salesforce/schema/Account.acme__Relationship__r.acme__CustomField__c
|
|
319
|
+
// @salesforce/resourceUrl/resource1 -> @salesforce/resourceUrl/namespace__resource1
|
|
320
|
+
// @salesforce/contentAssetUrl/asset -> @salesforce/contentAssetUrl/nsC__asset;
|
|
321
|
+
// @salesforce/customPermission/perm -> @salesforce/customPermission/namespace__perm;
|
|
322
|
+
// @salesforce/komaci/resource1 -> @salesforce/komaci/namespace__resource1
|
|
323
|
+
// @salesforce/messageChannel/messageChannel__c -> @salesforce/messageChannel/namespace__messageChannel__c;
|
|
324
|
+
case 'schema':
|
|
325
|
+
case 'resourceUrl':
|
|
326
|
+
case 'contentAssetUrl':
|
|
327
|
+
case 'customPermission':
|
|
328
|
+
case 'komaci':
|
|
329
|
+
case 'messageChannel':
|
|
330
|
+
isExplicitNamespaceUsed = value.includes(`${explicitNamespace}__`);
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
// @salesforce/apexContinuation/MyClass.methodA -> @salesforce/apexContinuation/acme.MyClass.methodA
|
|
334
|
+
// @salesforce/apex/MyClass.methodA -> @salesforce/apex/acme.MyClass.methodA
|
|
335
|
+
// The original resource type is 'apex', however, when we collect the reference
|
|
336
|
+
// we always mark it as an apexMethod.
|
|
337
|
+
case 'apexContinuation':
|
|
338
|
+
case 'apexMethod': {
|
|
339
|
+
// If the namespace is present the value would have the following form <ns>.<class>.<method>
|
|
340
|
+
// otherwise it would be <class>.<method>.
|
|
341
|
+
const parts = value.split('.');
|
|
342
|
+
if (parts.length === 3) {
|
|
343
|
+
isExplicitNamespaceUsed = parts[0] === explicitNamespace;
|
|
344
|
+
}
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// following resources are never namespaced
|
|
349
|
+
case 'client':
|
|
350
|
+
case 'cssvars':
|
|
351
|
+
case 'gate':
|
|
352
|
+
case 'i18n':
|
|
353
|
+
case 'metric':
|
|
354
|
+
case 'user':
|
|
355
|
+
case 'commerce':
|
|
356
|
+
case 'community':
|
|
357
|
+
case 'site':
|
|
358
|
+
case 'internal':
|
|
359
|
+
case 'userPermission':
|
|
360
|
+
break;
|
|
361
|
+
default:
|
|
362
|
+
throw new TypeError(`Unsupported reference type ${type}.`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return isExplicitNamespaceUsed;
|
|
366
|
+
}
|