Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

@lwc/babel-plugin-component

Package Overview
Dependencies
Maintainers
11
Versions
784
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lwc/babel-plugin-component - npm Package Compare versions

Comparing version 0.35.6-alpha1 to 0.35.6

src/__tests__/export-metadata.spec.js

6

package.json
{
"name": "@lwc/babel-plugin-component",
"description": "Babel plugin to transform a LWC module",
"version": "0.35.6-alpha1",
"version": "0.35.6",
"main": "src/index.js",

@@ -14,3 +14,3 @@ "typings": "types/index.d.ts",

"@babel/plugin-proposal-class-properties": "7.1.0",
"@lwc/errors": "0.35.6-alpha1"
"@lwc/errors": "0.35.6"
},

@@ -23,3 +23,3 @@ "devDependencies": {

},
"gitHead": "5aa40c386dfdac48b9db4afb7a7c5711b81ebc3a"
"gitHead": "b9a3237c080ec83ec8223b33eaccf440cac4dbb3"
}

@@ -618,1 +618,201 @@ /*

}
describe('Metadata', () => {
apiValueMetadataTest(
'gather metadata for static "string" property value',
`@api staticString = "string value";`,
{
type: "property",
name: "staticString",
loc: {
end: { line: 2, column: 87 },
start: { line: 2, column: 52 },
},
decorator: "api",
value: { type: "string", value:"string value" },
},
);
apiValueMetadataTest(
'gather metadata for static "boolean" property value',
`@api staticBoolean = false;`,
{
type: "property",
name: "staticBoolean",
loc: {
end: { line: 2, column: 79 },
start: { line: 2, column: 52 },
},
decorator: "api",
value: { type: "boolean", value: false },
},
);
apiValueMetadataTest(
'gather metadata for static "numeric" property value',
`@api numeric = 0;`,
{
decorator: "api",
loc: {
end: { column: 69, line: 2 },
start: { column: 52, line: 2 },
},
name: "numeric",
type: "property",
value: { type: "number", value: 0 }
},
);
apiValueMetadataTest(
'gather metadata for static property without value',
`@api nodefault`,
{
decorator: "api",
loc: {
end: { column: 66, line: 2 },
start: { column: 52, line: 2 },
},
name: "nodefault",
type: "property",
value: { type: "unresolved", value: undefined }
},
);
apiValueMetadataTest(
'gather metadata for static property with "undefined" value',
`@api staticUndefined = undefined;`,
{
decorator: "api",
loc: {
end: { column: 85, line: 2 },
start: { column: 52, line: 2 },
},
name: "staticUndefined",
type: "property",
value: { type: "unresolved", value: undefined }
},
);
apiValueMetadataTest(
'gather metadata for static property with "null" value',
`@api staticNull = null;`,
{
decorator: "api",
loc: {
end: { column: 75, line: 2 },
start: { column: 52, line: 2 },
},
name: "staticNull",
type: "property",
value: { type: "null", value: null }
},
);
apiValueMetadataTest(
'gather metadata for static property with "object" value',
`@api staticObject = {
stringProp: "string property",
numericProp: 0,
booleanProp: true,
arrayProp: [],
objectProp: {},
};`,
{
decorator: "api",
loc: {
end: { column: 2, line: 8 },
start: { column: 52, line: 2 },
},
name: "staticObject",
type: "property",
value: {
type: "object",
value: {
stringProp: {
type: "string",
value: "string property",
},
numericProp: {
type: "number",
value: 0,
},
booleanProp: {
type: "boolean",
value: true,
},
arrayProp: {
type: "array",
value: [],
},
objectProp: {
type: "object",
value: {},
},
}
},
},
);
apiValueMetadataTest(
'gather metadata for static property with "array" value',
`@api staticArray = ['stringval', 0, true, null, undefined, {}, []];;`,
{
decorator: "api",
loc: {
end: { column: 119, line: 2 },
start: { column: 52, line: 2 },
},
name: "staticArray",
type: "property",
value: {
type: "array",
value: [
{ type: "string", value: "stringval" },
{ type: "number", value: 0 },
{ type: "boolean", value: true },
{ type: "null", value: null },
{ type: "unresolved", value: undefined },
{ type: "object", value: {} },
{ type: "array", value: [] },
]
}
},
);
pluginTest(
'does not gather metadata value for public setter and getter methods',
`
import { LightningElement, api } from 'lwc';
export default class Foo extends LightningElement {
_privateTodo;
get todo () { return this._privateTodo; }
@api set todo (val) { return this._privateTodo = val; }
}
`,
{
output: {
metadata: {
classMembers: [
{
decorator: "api",
loc: {
end: { column: 55, line: 5 },
start: { column: 0, line: 5 }
},
name: "todo",
type: "property"
}
],
declarationLoc: {
end: { column: 1, line: 6 },
start: { column: 0, line: 2 }
},
decorators: [
{ targets: [{ name: "todo", type: "property" }], type: "api" }
],
exports: [{ type: "ExportDefaultDeclaration" }]
}
}
}
);
});

@@ -180,1 +180,341 @@ /*

});
describe('metadata', () => {
pluginTest('class jsdoc single line (and declarationLoc)', `
import { LightningElement } from 'lwc';
/** Foo doc */
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
declarationLoc: { start: { line: 3, column: 0 }, end: { line: 4, column: 1 }},
doc: '* Foo doc',
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
});
pluginTest('class jsdoc', `
import { LightningElement } from 'lwc';
/**
* Foo doc
*/
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
declarationLoc: { end: { column: 1, line: 6 }, start: { column: 0, line: 5 } },
doc: "*\n* Foo doc",
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
});
pluginTest('class jsdoc multiline', `
import { LightningElement } from 'lwc';
/**
* multi
* line
*/
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
declarationLoc: { end: { column: 1, line: 7 }, start: { column: 0, line: 6 } },
doc: '*\n* multi\n* line',
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
});
pluginTest('class jsdoc multi comments', `
import { LightningElement } from 'lwc';
/** first */
/** last */
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
declarationLoc: { end: { column: 1, line: 5 }, start: { column: 0, line: 4 } },
doc: "* last",
exports: [{ type: 'ExportDefaultDeclaration' }],
},
}
});
pluginTest('class jsdoc empty', `
import { LightningElement } from 'lwc';
/** */
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
doc: "*",
declarationLoc: {
end: { column: 1, line: 4 },
start: { column: 0, line: 3 }
},
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
});
pluginTest('class no-jsdoc /**/', `
import { LightningElement } from 'lwc';
/* not a jsdoc */
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
declarationLoc: {
end: { column: 1, line: 4 },
start: { column: 0, line: 3 }
},
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
});
pluginTest('class no-jsdoc //', `
import { LightningElement } from 'lwc';
// not a jsdoc
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
declarationLoc: {
end: { column: 1, line: 4 },
start: { column: 0, line: 3 }
},
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
});
pluginTest('json in comment', `
import { LightningElement } from 'lwc';
/** { one: "1", two: '2', array: [1, 2, 3]} */
export default class Foo extends LightningElement {
}
`, {
output: {
metadata: {
decorators: [],
classMembers: [],
declarationLoc: {
end: { column: 1, line: 4 },
start: { column: 0, line: 3 }
},
doc: "* { one: \"1\", two: '2', array: [1, 2, 3]}",
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
});
pluginTest(
"tooling metadata",
`
import { LightningElement, api, track } from 'lwc';
// not a jsdoc
export default class Foo extends LightningElement {
@track state;
/** property-comment */ @api publicProp;
/** method-comment */ @api publicMethod() {}
privateProp;
privateMethod() {}
_privateTodo;
@api
get todo () {return this._privateTodo;}
set todo (val) {return this._privateTodo = val;}
}
`,
{
output: {
metadata: {
decorators: [
{
type: "api",
targets: [
{ type: "property", name: "publicProp", value: { type: "unresolved", value: undefined } },
{ type: "property", name: "todo" },
{ type: "method", name: "publicMethod" }
]
},
{
type: "track",
targets: [
{ type: "property", name: "state" }
]
}
],
classMembers: [
{
type: "property",
name: "state",
loc: {
start: { line: 4, column: 0 },
end: { line: 4, column: 13 }
},
decorator: "track"
},
{
type: "property",
name: "publicProp",
value: { type: "unresolved", value: undefined },
loc: {
start: { line: 5, column: 24 },
end: { line: 5, column: 40 }
},
doc: "* property-comment",
decorator: "api"
},
{
type: "method",
name: "publicMethod",
loc: {
start: { line: 6, column: 22 },
end: { line: 6, column: 44 }
},
doc: "* method-comment",
decorator: "api"
},
{
type: "property",
name: "privateProp",
loc: {
start: { line: 7, column: 0 },
end: { line: 7, column: 12 }
}
},
{
type: "method",
name: "privateMethod",
loc: {
start: { line: 8, column: 0 },
end: { line: 8, column: 18 }
}
},
{
type: "property",
name: "todo",
loc: {
start: { line: 10, column: 0 },
end: { line: 11, column: 39 }
},
decorator: "api"
}
],
declarationLoc: {
start: { line: 3, column: 0 },
end: { line: 13, column: 1 }
},
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
}
);
pluginTest(
"@api on getter only",
`
import { LightningElement, api} from 'lwc';
export default class Foo extends LightningElement {
@api
get todo () {return this._privateTodo;}
set todo (val) {return this._privateTodo = val;}
}
`,
{
output: {
metadata: {
decorators: [
{
type: "api",
targets: [
{ type: "property", name: "todo" },
]
}
],
classMembers: [
{
type: "property",
name: "todo",
loc: {
start: { line: 3, column: 0 },
end: { line: 4, column: 39 }
},
decorator: "api"
}
],
declarationLoc: {
start: { line: 2, column: 0 },
end: { line: 6, column: 1 }
},
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
}
);
pluginTest(
"@api on getter only without a setter pair",
`
import { LightningElement, api} from 'lwc';
export default class Foo extends LightningElement {
@api
get todo () {return this._privateTodo;}
}
`,
{
output: {
metadata: {
decorators: [
{
type: "api",
targets: [
{ type: "property", name: "todo" },
]
}
],
classMembers: [
{
type: "property",
name: "todo",
loc: {
start: { line: 3, column: 0 },
end: { line: 4, column: 39 }
},
decorator: "api"
}
],
declarationLoc: {
start: { line: 2, column: 0 },
end: { line: 5, column: 1 }
},
exports: [{ type: 'ExportDefaultDeclaration' }],
}
}
}
);
});

@@ -23,3 +23,3 @@ /*

import { registerComponent as _registerComponent } from "lwc";
class Test {

@@ -30,3 +30,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -37,3 +37,3 @@ track: {

});
export default _registerComponent(Test, {

@@ -59,3 +59,3 @@ tmpl: _tmpl

import { registerComponent as _registerComponent } from "lwc";
class Test {

@@ -68,3 +68,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -75,3 +75,3 @@ track: {

});
export default _registerComponent(Test, {

@@ -140,1 +140,45 @@ tmpl: _tmpl

});
describe('Metadata', () => {
pluginTest(
'gather metadata',
`
import { LightningElement, track } from 'lwc';
export default class Test extends LightningElement {
@track state;
}
`,
{
output: {
metadata: {
decorators: [
{
type: "track",
targets: [{ name: "state", type: "property" }]
}
],
classMembers: [
{
type: "property",
name: "state",
loc: {
start: { line: 3, column: 0 },
end: { line: 3, column: 13 }
},
decorator: "track"
}
],
declarationLoc: {
end: { column: 1, line: 4 },
start: { column: 0, line: 2 }
},
exports: [
{
type: 'ExportDefaultDeclaration',
}
],
}
}
}
);
});

@@ -60,2 +60,5 @@ /*

}
if (expected.output.metadata) {
expect(output.metadata).toEqual(expected.output.metadata);
}
} else {

@@ -62,0 +65,0 @@ throw new TypeError(`Transform test expect an object with either error or output.`);

@@ -9,2 +9,41 @@ /*

const wireMetadataParameterTest =
(testName, {declaration = '', wireParameters = '', expectedStaticParameters = {}, expectedVariableParameters = {}}) => {
pluginTest(
testName,
`
import { wire } from 'lwc';
import { getRecord } from 'recordDataService';
${declaration};
export default class Test {
@wire(getRecord, { ${wireParameters.join(',')} })
recordData;
}
`,
{
output: {
metadata: {
decorators: [{
type: 'wire',
targets: [
{
adapter: { name: 'getRecord', reference: 'recordDataService' },
name: 'recordData',
params: expectedVariableParameters,
static: expectedStaticParameters ,
type: 'property',
}
],
}],
exports: [
{
type: 'ExportDefaultDeclaration',
}
],
},
},
},
);
};
describe('Transform property', () => {

@@ -25,3 +64,3 @@ pluginTest('transforms wired field', `

import { getFoo } from "data-service";
class Test {

@@ -32,3 +71,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -47,3 +86,3 @@ wire: {

});
export default _registerComponent(Test, {

@@ -70,3 +109,3 @@ tmpl: _tmpl

import { getFoo } from "data-service";
class Test {

@@ -77,3 +116,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -94,3 +133,3 @@ wire: {

});
export default _registerComponent(Test, {

@@ -131,3 +170,3 @@ tmpl: _tmpl

import { getFoo } from "data-service";
class Test {

@@ -138,3 +177,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -149,3 +188,3 @@ wire: {

});
export default _registerComponent(Test, {

@@ -171,3 +210,3 @@ tmpl: _tmpl

import getFoo from "foo";
class Test {

@@ -178,3 +217,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -189,3 +228,3 @@ wire: {

});
export default _registerComponent(Test, {

@@ -211,3 +250,3 @@ tmpl: _tmpl

import { getFoo } from "data-service";
class Test {

@@ -218,3 +257,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -227,3 +266,3 @@ wire: {

});
export default _registerComponent(Test, {

@@ -338,3 +377,3 @@ tmpl: _tmpl

import { getFoo } from "data-service";
class Test {

@@ -346,3 +385,3 @@ constructor() {

}
_registerDecorators(Test, {

@@ -370,3 +409,3 @@ wire: {

});
export default _registerComponent(Test, {

@@ -395,7 +434,7 @@ tmpl: _tmpl

import { getFoo } from "data-service";
class Test {
wiredMethod() {}
}
_registerDecorators(Test, {

@@ -415,3 +454,3 @@ wire: {

});
export default _registerComponent(Test, {

@@ -442,1 +481,133 @@ tmpl: _tmpl

});
describe('Metadata', () => {
pluginTest(
'gather wire metadata',
`
import { wire } from 'lwc';
import { getFoo } from 'data-service';
export default class Test {
@wire(getFoo, { key1: "$prop1", key2: ["fixed"]})
wiredProp;
@wire(getFoo, { key1: "$prop1", key2: ["fixed"]})
wiredMethod() {}
}
`,
{
output: {
metadata: {
decorators: [{
type: 'wire',
targets: [{
adapter: { name: 'getFoo', reference: 'data-service' },
name: 'wiredProp',
params: { key1: 'prop1' },
static: { key2: { value: ['fixed'], type: 'array' } },
type: 'property',
},
{
adapter: { name: 'getFoo', reference: 'data-service' },
name: 'wiredMethod',
params: { key1: 'prop1' },
static: { key2: { value: ['fixed'], type: 'array' } },
type: 'method',
}],
}],
exports: [
{
type: 'ExportDefaultDeclaration',
}
],
},
},
},
);
wireMetadataParameterTest('when constant initialised to a string-literal should extract the value',
{ declaration: `const stringConstant = '123';`,
wireParameters: ['userId: stringConstant'],
expectedStaticParameters: { userId: { value: '123', type: 'string'} } });
wireMetadataParameterTest('when constant initialised to a number-literal should extract the value',
{ declaration: `const numberConstant = 100;`,
wireParameters: ['size: numberConstant'],
expectedStaticParameters: { size: { value: 100, type: 'number'} } });
wireMetadataParameterTest('when constant initialised to a boolean-literal should extract the value',
{ declaration: `const booleanConstant = true;`,
wireParameters: ['isRegistered: booleanConstant'],
expectedStaticParameters: { isRegistered: { value: true, type: 'boolean'} } });
wireMetadataParameterTest('when constant initialised as a reference to another should mark as unresolved',
{ declaration: `const stringConstant = '123'; const referenceConstant = stringConstant;`,
wireParameters: ['recordId: referenceConstant'],
expectedStaticParameters: { recordId: { type: 'unresolved', value: 'identifier' } } });
wireMetadataParameterTest('when importing a default export from a module should reference the name of the module',
{ declaration: `import id from '@salesforce/user/Id';`,
wireParameters: ['recordId: id'],
expectedStaticParameters: { recordId: { value: '@salesforce/user/Id', type: 'module' } } });
wireMetadataParameterTest('when parameter reference missing should mark as unresolved',
{ wireParameters: ['recordId: id'],
expectedStaticParameters: { recordId: { type: 'unresolved', value: 'identifier'} } });
wireMetadataParameterTest('when importing named export with "as" from a module should reference the name of the module',
{ declaration: `import { id as currentUserId } from '@salesforce/user/Id';`,
wireParameters: ['recordId: currentUserId'],
expectedStaticParameters: { recordId: { value: '@salesforce/user/Id', type: 'module' } } });
wireMetadataParameterTest('when importing a named export from a module should reference the name of the module',
{ declaration: `import { id } from '@salesforce/user/Id';`,
wireParameters: ['recordId: id'],
expectedStaticParameters: { recordId: { value: '@salesforce/user/Id', type: 'module' } } });
wireMetadataParameterTest('when importing from a relative module should reference the name of the module',
{ declaration: `import id from './someReference.js';`,
wireParameters: ['recordId: id'],
expectedStaticParameters: { recordId: { value: './someReference.js', type: 'module' } } });
wireMetadataParameterTest('when referencing a "let" variable should mark as unresolved',
{ declaration: `let userId = '123';`,
wireParameters: ['recordId: userId'],
expectedStaticParameters: { recordId: { type: 'unresolved', value: 'identifier'} } });
wireMetadataParameterTest('when referencing a member expression, should mark as unresolved',
{ declaration: `const data = {userId: '123'};`,
wireParameters: ['recordId: data.userId'],
expectedStaticParameters: { recordId: { type: 'unresolved', value: 'member_expression' } } });
wireMetadataParameterTest('when an inline string-literal initialization is used, should use value',
{ wireParameters: ['recordId: "123"'],
expectedStaticParameters: { recordId: { value: '123', type: 'string' } } });
wireMetadataParameterTest('when an inline numeric-literal initialization is used, should use value',
{ wireParameters: ['size: 100'],
expectedStaticParameters: { size: { value: 100, type: 'number' } } });
wireMetadataParameterTest('when an inline float-literal initialization is used, should use value',
{ wireParameters: ['underPrice: 50.50'],
expectedStaticParameters: { underPrice: { value: 50.50, type: 'number' } } });
wireMetadataParameterTest('when an inline boolean-literal initialization is used, should use value',
{ wireParameters: ['isRegistered: true'],
expectedStaticParameters: { isRegistered: { value: true, type: 'boolean' } } });
wireMetadataParameterTest('when $foo parameters are used, should use name of the parameter',
{ wireParameters: ['recordId: "$userId"'],
expectedVariableParameters: { recordId: 'userId' } });
wireMetadataParameterTest('when $foo parameters with dots are used, should use name of the parameter',
{ wireParameters: ['recordId: "$userRecord.Id"'],
expectedVariableParameters: { recordId: 'userRecord.Id' } });
wireMetadataParameterTest('when inline array with a non-string literal is used, should mark as unresolved',
{ declaration: `const bar = '123';`,
wireParameters: ['fields: ["foo", bar]'],
expectedStaticParameters: { fields: {type: 'unresolved', value: 'array_expression'} } });
wireMetadataParameterTest('when inline array with literals is used, should have the array',
{wireParameters: ['data: ["foo", 123, false]'],
expectedStaticParameters: { data: {type: 'array', value: ['foo', 123, false]} } });
});

@@ -8,4 +8,5 @@ /*

const { generateError, getEngineImportSpecifiers } = require('./utils');
const { LWC_PACKAGE_EXPORTS, LWC_API_WHITELIST } = require('./constants');
const { GLOBAL_ATTRIBUTE_MAP, LWC_PACKAGE_EXPORTS, LWC_API_WHITELIST } = require('./constants');
const { LWCClassErrors } = require('@lwc/errors');
const CLASS_PROPERTY_OBSERVED_ATTRIBUTES = 'observedAttributes';

@@ -12,0 +13,0 @@ module.exports = function ({ types: t }) {

@@ -7,3 +7,28 @@ /*

*/
function globalAttributeObject(propName) {
return {
propName,
}
}
const GLOBAL_ATTRIBUTE_MAP = new Map([
['role', globalAttributeObject('role')],
['accesskey', globalAttributeObject('accessKey')],
['class', globalAttributeObject('class')],
['contenteditable', globalAttributeObject('contentEditable')],
['contextmenu', globalAttributeObject('contextmenu')],
['dir', globalAttributeObject('dir')],
['draggable', globalAttributeObject('draggable')],
['dropzone', globalAttributeObject('dropzone')],
['hidden', globalAttributeObject('hidden')],
['id', globalAttributeObject('id')],
['itemprop', globalAttributeObject('itemprop')],
['lang', globalAttributeObject('lang')],
['slot', globalAttributeObject('slot')],
['spellcheck', globalAttributeObject('spellcheck')],
['style', globalAttributeObject('style')],
['tabindex', globalAttributeObject('tabIndex')],
['title', globalAttributeObject('title')],
]);
// This set is for attributes that have a camel cased property name

@@ -78,2 +103,3 @@ // For example, div.tabIndex.

DISALLOWED_PROP_SET,
GLOBAL_ATTRIBUTE_MAP,
LWC_DECORATORS,

@@ -83,4 +109,5 @@ LWC_PACKAGE_ALIAS,

LWC_API_WHITELIST,
LWC_COMPONENT_PROPERTIES,
DECORATOR_TYPES,
};

@@ -8,3 +8,3 @@ /*

const { isApiDecorator } = require('./shared');
const { markAsLWCNode, staticClassProperty } = require('../../utils');
const { extractValueMetadata, markAsLWCNode, staticClassProperty } = require('../../utils');
const { LWC_COMPONENT_PROPERTIES: { PUBLIC_PROPS, PUBLIC_METHODS }, DECORATOR_TYPES } = require('../../constants');

@@ -91,2 +91,22 @@

}
return publicProps.filter((node) => {
const { type } = node;
return type === 'getter' || type === 'setter' || type === 'property';
}).map((node) => {
const { path, type } = node;
const parentNode = path.parent;
const parentValue = parentNode && parentNode.value;
const meta = {
type: 'property',
name: path.parentPath.get('key.name').node,
}
if (type === 'property') {
meta.value = extractValueMetadata(parentValue);
}
return meta;
});
}

@@ -108,2 +128,7 @@

}
return publicMethods.map(({ path }) => ({
type: 'method',
name: path.parentPath.get('key.name').node
}));
}

@@ -115,5 +140,11 @@

transformPublicProps(t, klassBody, apiDecorators);
transformPublicMethods(t, klassBody, apiDecorators);
const apiProperties = transformPublicProps(t, klassBody, apiDecorators);
const apiMethods = transformPublicMethods(t, klassBody, apiDecorators);
if ((apiProperties.length + apiMethods.length) > 0) {
return {
type: 'api',
targets: [...apiProperties, ...apiMethods]
};
}
}

@@ -110,7 +110,13 @@ /*

/** Transform the decorators */
/** Transform the decorators and returns the metadata */
function transform(t, klass, decorators) {
return DECORATOR_TRANSFORMS.forEach(({ transform }) => {
transform(t, klass, decorators);
});
return DECORATOR_TRANSFORMS.reduce((metadata, { transform }) => {
const decoratorMetadata = transform(t, klass, decorators);
if (decoratorMetadata) {
metadata.push(decoratorMetadata);
}
return metadata;
}, []);
}

@@ -157,2 +163,3 @@

const decorators = getLwcDecorators(decoratorImportSpecifiers);
state.file.metadata = Object.assign({}, state.file.metadata, { decorators: [] });
const grouped = groupDecorator(decorators);

@@ -162,3 +169,7 @@

validate(klass, decorators);
transform(t, klass, decorators);
// Note: In the (extremely rare) case of multiple classes in the same file, only the metadata about the
// last class will be returned
const metadata = transform(t, klass, decorators);
state.file.metadata.decorators.push(...metadata);
}

@@ -171,2 +182,3 @@

Class(path, state) {
// don't remove decorators until metadata.js had the chance to visit the Class node
removeDecorators(state.decorators);

@@ -173,0 +185,0 @@ removeImportSpecifiers(state.decoratorImportSpecifiers);

@@ -53,2 +53,10 @@ /*

);
return {
type: 'track',
targets: trackProperties.map(name => ({
name,
type: 'property',
}))
};
}

@@ -55,0 +63,0 @@ }

@@ -83,3 +83,3 @@ /*

const SUPPORTED_VALUE_TO_TYPE_MAP = {
const SUPPORTED_VALUE_TYPE_TO_METADATA_TYPE = {
StringLiteral: 'string',

@@ -90,3 +90,61 @@ NumericLiteral: 'number',

function getWiredStaticMetadata(properties, referenceLookup) {
const ret = {};
properties.forEach(s => {
let result = {};
const valueType = s.value.type;
if (s.key.type === 'Identifier') {
if (valueType === 'ArrayExpression') {
// @wire(getRecord, { fields: ['Id', 'Name'] })
// @wire(getRecord, { data: [123, false, 'string'] })
const elements = s.value.elements;
const hasUnsupportedElement =
elements.some(element => !SUPPORTED_VALUE_TYPE_TO_METADATA_TYPE[element.type]);
if (hasUnsupportedElement) {
result = {type: 'unresolved', value: 'array_expression'};
} else {
result = {type: 'array', value: elements.map(e => e.value)};
}
} else if (SUPPORTED_VALUE_TYPE_TO_METADATA_TYPE[valueType]) {
// @wire(getRecord, { companyName: ['Acme'] })
// @wire(getRecord, { size: 100 })
// @wire(getRecord, { isAdmin: true })
result = {type: SUPPORTED_VALUE_TYPE_TO_METADATA_TYPE[valueType], value: s.value.value};
} else if (valueType === 'Identifier') {
// References such as:
// 1. Modules
// import id from '@salesforce/user/id'
// @wire(getRecord, { userId: id })
//
// 2. 1st order constant references with string literals
// const userId = '123';
// @wire(getRecord, { userId: userId })
const reference = referenceLookup(s.value.name);
result = {value: reference.value, type: reference.type};
if (!result.type) {
result = {type: 'unresolved', value: 'identifier'}
}
} else if (valueType === 'MemberExpression') {
// @wire(getRecord, { userId: recordData.Id })
result = {type: 'unresolved', value: 'member_expression'};
}
}
if (!result.type) {
result = {type: 'unresolved'};
}
ret[s.key.name] = result;
});
return ret;
}
function getWiredParamMetadata(properties) {
const ret = {};
properties.forEach(p => {
if (p.key.type === 'Identifier' && p.value.type === 'StringLiteral') {
ret[p.key.name] = p.value.value;
}
});
return ret;
}
const scopedReferenceLookup = scope => name => {

@@ -110,4 +168,4 @@ const binding = scope.getBinding(name);

const init = binding.path.node.init;
if (init && SUPPORTED_VALUE_TO_TYPE_MAP[init.type]) {
type = SUPPORTED_VALUE_TO_TYPE_MAP[init.type];
if (init && SUPPORTED_VALUE_TYPE_TO_METADATA_TYPE[init.type]) {
type = SUPPORTED_VALUE_TYPE_TO_METADATA_TYPE[init.type];
value = init.value;

@@ -124,2 +182,3 @@ }

module.exports = function transform(t, klass, decorators) {
const metadata = [];
const wiredValues = decorators.filter(isWireDecorator).map(({path}) => {

@@ -154,2 +213,15 @@ const [id, config] = path.get('expression.arguments');

const wireMetadata = {
name: wiredValue.propertyName,
adapter: wiredValue.adapter,
type: isClassMethod ? 'method' : 'property'
};
if (config) {
wireMetadata.static = getWiredStaticMetadata(wiredValue.static, referenceLookup);
wireMetadata.params = getWiredParamMetadata(wiredValue.params);
}
metadata.push(wireMetadata);
return wiredValue;

@@ -169,2 +241,9 @@ });

}
if (metadata.length > 0) {
return {
type: 'wire',
targets: metadata
};
}
};

@@ -7,2 +7,3 @@ /*

*/
const metadata = require('./metadata');
const component = require('./component');

@@ -26,2 +27,3 @@ const { decorators } = require('./decorators');

visitor: mergeVisitors([
metadata(api),
decorators(api),

@@ -28,0 +30,0 @@ component(api),

@@ -14,2 +14,14 @@ /*

function findClassMethod(path, name, properties = {}) {
path.assertClassBody();
return path.get('body').find(path => (
isClassMethod(path, {
name,
kind: properties.kind || 'method',
static: properties.static
})
));
}
function isClassMethod(classMethod, properties = {}) {

@@ -53,2 +65,59 @@ const { kind = 'method', name } = properties;

function getExportedNames(path) {
const programPath = path.isProgram() ? path : path.findParent(node => node.isProgram());
return exports = programPath.get('body').reduce((names, node) => {
const exportSource = getExportSrc(node && node.node.source);
// export default class App {}
if (node.isExportDefaultDeclaration()) {
names.push(createModuleExportInfo({ type: EXPORT_DEFAULT_DECLARATION, source: exportSource }));
// export * from 'external-module'
} else if (node.isExportDeclaration() && node.type === EXPORT_ALL_DECLARATION) {
names.push(createModuleExportInfo({ type: EXPORT_ALL_DECLARATION, source: exportSource }));
} else if (node.isExportDeclaration() && node.type === EXPORT_NAMED_DECLARATION) {
// export { method } from 'utils'
const specifiers = node.node.specifiers;
if (Array.isArray(specifiers)) {
specifiers.forEach(specifier => {
const exportValue = specifier.exported.name;
names.push(createModuleExportInfo({
type: EXPORT_NAMED_DECLARATION,
value: exportValue,
source: exportSource
}));
});
}
const declaration = node.node.declaration;
if (declaration) {
// export const version = 0;
if (declaration.type === 'VariableDeclaration' && Array.isArray(declaration.declarations)) {
declaration.declarations.forEach(nameDeclaration => {
exportValue = nameDeclaration.id.name;
});
// export class Inner {};
} else if (declaration.type === 'ClassDeclaration'
|| declaration.type === 'FunctionDeclaration') {
exportValue = declaration.id.name;
}
names.push(createModuleExportInfo({
type: EXPORT_NAMED_DECLARATION,
value: exportValue,
source: exportSource
}));
}
}
return names;
}, []);
}
function getExportSrc(src) {

@@ -102,2 +171,18 @@ if (!src || !src.value) {

function isComponentClass(classPath, componentBaseClassImports) {
const superClass = classPath.get('superClass');
return superClass.isIdentifier() && componentBaseClassImports
&& componentBaseClassImports.some(componentBaseClassImport => (
classPath.scope.bindingIdentifierEquals(
superClass.node.name,
componentBaseClassImport.node
)
));
}
function isDefaultExport(path) {
return path.parentPath.isExportDefaultDeclaration();
}
function generateError(source, { errorInfo, messageArgs } = {}) {

@@ -119,5 +204,100 @@ const message = generateErrorMessage(errorInfo, messageArgs);

function extractValueMetadata(valueNode) {
let valueMetadata = {
type: 'unresolved',
value: undefined,
};
if (!valueNode) {
return valueMetadata;
}
const { type } = valueNode;
if (type === 'StringLiteral') {
valueMetadata = extractStringValueMeta(valueNode);
} else if (type === 'NumericLiteral') {
valueMetadata = extractNumberValueMeta(valueNode);
} else if (type === 'BooleanLiteral') {
valueMetadata = extractBooleanValueMeta(valueNode);
} else if (type === 'NullLiteral') {
valueMetadata = {
type: "null",
value: null,
};
} else if (type === 'ObjectExpression') {
valueMetadata = extractObjectValueMeta(valueNode);
} else if (type === 'ArrayExpression') {
valueMetadata = extractArrayValueMeta(valueNode);
}
return valueMetadata;
}
function extractStringValueMeta(valueNode) {
return {
type: 'string',
value: valueNode && valueNode.value || undefined,
}
}
function extractNumberValueMeta(valueNode) {
let value = valueNode && valueNode.value;
return {
type: 'number',
value: value === null ? undefined : value
}
}
function extractBooleanValueMeta(valueNode) {
let value = valueNode && valueNode.value;
return {
type: 'boolean',
value: !!(valueNode && valueNode.value),
}
}
function extractArrayValueMeta(valueNode) {
const arrayValueMeta = {
type: 'array',
value: [],
}
if (!valueNode) {
return arrayValueMeta;
}
return {
type: 'array',
value: valueNode.elements.map((elem) => extractValueMetadata(elem)),
}
}
function extractObjectValueMeta(valueNode) {
const objectValueMeta = {
type: 'object',
value: {},
}
if (!valueNode) {
return objectValueMeta;
}
const values = {};
valueNode.properties.forEach(({key, value}) => {
values[key.name] = extractValueMetadata(value);
});
return {
type: 'object',
value: values,
}
}
module.exports = {
isLWCNode,
markAsLWCNode,
findClassMethod,
isClassMethod,

@@ -129,2 +309,6 @@ isGetterClassMethod,

generateError,
isComponentClass,
isDefaultExport,
getExportedNames,
extractValueMetadata,
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc