diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/error.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/expected.html new file mode 100644 index 0000000000..4b6d956f1a --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/expected.html @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/index.js new file mode 100644 index 0000000000..d5a55ceefa --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/index.js @@ -0,0 +1,3 @@ +export const tagName = 'x-parent'; +export { default } from 'x/parent'; +export * from 'x/parent'; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/child/child.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/child/child.html new file mode 100755 index 0000000000..9a178c0e77 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/child/child.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/child/child.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/child/child.js new file mode 100755 index 0000000000..7c97dd51ea --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/child/child.js @@ -0,0 +1,12 @@ +import { LightningElement, api } from 'lwc'; + +export default class Child extends LightningElement { + set setterGetterApi(value) { + this._someApi = value; + } + + @api + get setterGetterApi() { + return this._someApi; + } +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/parent/parent.html new file mode 100755 index 0000000000..f096b285f5 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/parent/parent.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/parent/parent.js new file mode 100755 index 0000000000..e1d0ea7f96 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-getter/modules/x/parent/parent.js @@ -0,0 +1,5 @@ +import { LightningElement } from 'lwc'; + +export default class Parent extends LightningElement { + setterGetterApiValue = 'setter getter api value'; +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/error.txt new file mode 100644 index 0000000000..ab90d18fb4 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/error.txt @@ -0,0 +1 @@ +LWC1112: @api get setterGetterApi and @api set setterGetterApi detected in class declaration. Only one of the two needs to be decorated with @api. \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/expected.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/index.js new file mode 100644 index 0000000000..d5a55ceefa --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/index.js @@ -0,0 +1,3 @@ +export const tagName = 'x-parent'; +export { default } from 'x/parent'; +export * from 'x/parent'; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/child/child.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/child/child.html new file mode 100755 index 0000000000..9a178c0e77 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/child/child.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/child/child.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/child/child.js new file mode 100755 index 0000000000..608d8f4639 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/child/child.js @@ -0,0 +1,13 @@ +import { LightningElement, api } from 'lwc'; + +export default class Child extends LightningElement { + @api + set setterGetterApi(value) { + this._someApi = value; + } + + @api + get setterGetterApi() { + return this._someApi; + } +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/parent/parent.html new file mode 100755 index 0000000000..f096b285f5 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/parent/parent.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/parent/parent.js new file mode 100755 index 0000000000..e1d0ea7f96 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter-getter/modules/x/parent/parent.js @@ -0,0 +1,5 @@ +import { LightningElement } from 'lwc'; + +export default class Parent extends LightningElement { + setterGetterApiValue = 'setter getter api value'; +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/error.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/expected.html new file mode 100644 index 0000000000..4b6d956f1a --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/expected.html @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/index.js new file mode 100644 index 0000000000..d5a55ceefa --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/index.js @@ -0,0 +1,3 @@ +export const tagName = 'x-parent'; +export { default } from 'x/parent'; +export * from 'x/parent'; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/child/child.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/child/child.html new file mode 100755 index 0000000000..9a178c0e77 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/child/child.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/child/child.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/child/child.js new file mode 100755 index 0000000000..bb1cabad15 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/child/child.js @@ -0,0 +1,12 @@ +import { LightningElement, api } from 'lwc'; + +export default class Child extends LightningElement { + @api + set setterGetterApi(value) { + this._someApi = value; + } + + get setterGetterApi() { + return this._someApi; + } +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/parent/parent.html new file mode 100755 index 0000000000..f096b285f5 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/parent/parent.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/parent/parent.js new file mode 100755 index 0000000000..e1d0ea7f96 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/decorated-setter/modules/x/parent/parent.js @@ -0,0 +1,5 @@ +import { LightningElement } from 'lwc'; + +export default class Parent extends LightningElement { + setterGetterApiValue = 'setter getter api value'; +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/error.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/expected.html new file mode 100644 index 0000000000..73ad529907 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/expected.html @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/index.js new file mode 100644 index 0000000000..d5a55ceefa --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/index.js @@ -0,0 +1,3 @@ +export const tagName = 'x-parent'; +export { default } from 'x/parent'; +export * from 'x/parent'; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/child/child.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/child/child.html new file mode 100755 index 0000000000..c7953f5407 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/child/child.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/child/child.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/child/child.js new file mode 100755 index 0000000000..26d125ff03 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/child/child.js @@ -0,0 +1,5 @@ +import { LightningElement, api } from 'lwc'; + +export default class Child extends LightningElement { + @api fieldApi; +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/parent/parent.html new file mode 100755 index 0000000000..2a68acdb59 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/parent/parent.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/parent/parent.js new file mode 100755 index 0000000000..2594cd2bed --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/api/property/modules/x/parent/parent.js @@ -0,0 +1,5 @@ +import { LightningElement } from 'lwc'; + +export default class Parent extends LightningElement { + fieldApiValue = 'field api value'; +} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/reserved-keywords/modules/x/cmp/cmp.js b/packages/@lwc/engine-server/src/__tests__/fixtures/reserved-keywords/modules/x/cmp/cmp.js index 738e5d8707..80e30c3485 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/reserved-keywords/modules/x/cmp/cmp.js +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/reserved-keywords/modules/x/cmp/cmp.js @@ -1,7 +1,7 @@ import { LightningElement } from 'lwc'; -const privateFields = undefined; -const publicFields = undefined; +const privateProperties = undefined; +const publicProperties = undefined; const stylesheetScopeToken = undefined; const hasScopedStylesheets = undefined; const defaultScopedStylesheets = undefined; @@ -12,8 +12,8 @@ export default class extends LightningElement { Object.assign( {}, { - privateFields, - publicFields, + privateProperties, + publicProperties, stylesheetScopeToken, hasScopedStylesheets, defaultScopedStylesheets, diff --git a/packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts b/packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts index ce1fd11d7b..bddee319c5 100644 --- a/packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts +++ b/packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts @@ -16,8 +16,8 @@ import type { ComponentMetaState } from './types'; const bGenerateMarkup = esTemplate` // These variables may mix with component-authored variables, so should be reasonably unique - const __lwcPublicFields__ = new Set(${/*public fields*/ is.arrayExpression}); - const __lwcPrivateFields__ = new Set(${/*private fields*/ is.arrayExpression}); + const __lwcPublicProperties__ = new Set(${/*api*/ is.arrayExpression}); + const __lwcPrivateProperties__ = new Set(${/*private fields*/ is.arrayExpression}); async function* generateMarkup( tagName, @@ -43,8 +43,8 @@ const bGenerateMarkup = esTemplate` instance[__SYMBOL__SET_INTERNALS]( props, attrs, - __lwcPublicFields__, - __lwcPrivateFields__, + __lwcPublicProperties__, + __lwcPrivateProperties__, ); instance.isConnected = true; if (instance.connectedCallback) { @@ -101,7 +101,7 @@ export function addGenerateMarkupFunction( tagName: string, filename: string ) { - const { privateFields, publicFields, tmplExplicitImports } = state; + const { privateProperties, publicProperties, tmplExplicitImports } = state; // The default tag name represents the component name that's passed to the transformer. // This is needed to generate markup for dynamic components which are invoked through @@ -141,8 +141,8 @@ export function addGenerateMarkupFunction( ); program.body.push( ...bGenerateMarkup( - b.arrayExpression(publicFields.map(b.literal)), - b.arrayExpression(privateFields.map(b.literal)), + b.arrayExpression(publicProperties.map(b.literal)), + b.arrayExpression(privateProperties.map(b.literal)), defaultTagName, classIdentifier, connectWireAdapterCode diff --git a/packages/@lwc/ssr-compiler/src/compile-js/index.ts b/packages/@lwc/ssr-compiler/src/compile-js/index.ts index a6386dcadf..34a8e50c31 100644 --- a/packages/@lwc/ssr-compiler/src/compile-js/index.ts +++ b/packages/@lwc/ssr-compiler/src/compile-js/index.ts @@ -101,16 +101,16 @@ const visitors: Visitors = { validateUniqueDecorator(decorators); const decoratedExpression = decorators?.[0]?.expression; if (is.identifier(decoratedExpression) && decoratedExpression.name === 'api') { - state.publicFields.push(node.key.name); + state.publicProperties.push(node.key.name); } else if ( is.callExpression(decoratedExpression) && is.identifier(decoratedExpression.callee) && decoratedExpression.callee.name === 'wire' ) { catalogWireAdapters(path, state); - state.privateFields.push(node.key.name); + state.privateProperties.push(node.key.name); } else { - state.privateFields.push(node.key.name); + state.privateProperties.push(node.key.name); } if ( @@ -164,6 +164,14 @@ const visitors: Visitors = { } else { catalogWireAdapters(path, state); } + } else if (is.identifier(decoratedExpression) && decoratedExpression.name === 'api') { + if (state.publicProperties.includes(node.key.name)) { + // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler` + throw new Error( + `LWC1112: @api get ${node.key.name} and @api set ${node.key.name} detected in class declaration. Only one of the two needs to be decorated with @api.` + ); + } + state.publicProperties.push(node.key.name); } switch (node.key.name) { @@ -269,8 +277,8 @@ export default function compileJS( tmplExplicitImports: null, cssExplicitImports: null, staticStylesheetIds: null, - publicFields: [], - privateFields: [], + publicProperties: [], + privateProperties: [], wireAdapters: [], experimentalDynamicComponent: options.experimentalDynamicComponent, importManager: new ImportManager(), diff --git a/packages/@lwc/ssr-compiler/src/compile-js/types.ts b/packages/@lwc/ssr-compiler/src/compile-js/types.ts index 6ff7723f08..3690338a52 100644 --- a/packages/@lwc/ssr-compiler/src/compile-js/types.ts +++ b/packages/@lwc/ssr-compiler/src/compile-js/types.ts @@ -48,10 +48,10 @@ export interface ComponentMetaState { cssExplicitImports: Map | null; // the set of variable names associated with explicitly imported CSS files staticStylesheetIds: Set | null; - // the public (`@api`-annotated) fields of the component class - publicFields: Array; - // the private fields of the component class - privateFields: Array; + // the public (`@api`-annotated) properties of the component class + publicProperties: Array; + // the private properties of the component class + privateProperties: Array; // indicates whether the LightningElement has any wired props wireAdapters: WireAdapter[]; // dynamic imports configuration diff --git a/packages/@lwc/ssr-runtime/src/lightning-element.ts b/packages/@lwc/ssr-runtime/src/lightning-element.ts index 2c78d199a4..464830d247 100644 --- a/packages/@lwc/ssr-runtime/src/lightning-element.ts +++ b/packages/@lwc/ssr-runtime/src/lightning-element.ts @@ -78,8 +78,8 @@ export class LightningElement implements PropsAvailableAtConstruction { [SYMBOL__SET_INTERNALS]( props: Properties, attrs: Attributes, - publicFields: Set, - privateFields: Set + publicProperties: Set, + privateProperties: Set ) { this.#props = props; this.#attrs = attrs; @@ -91,9 +91,9 @@ export class LightningElement implements PropsAvailableAtConstruction { for (const propName of keys(props)) { const attrName = htmlPropertyToAttribute(propName); if ( - publicFields.has(propName) || + publicProperties.has(propName) || ((REFLECTIVE_GLOBAL_PROPERTY_SET.has(propName) || isAriaAttribute(attrName)) && - !privateFields.has(propName)) + !privateProperties.has(propName)) ) { // For props passed from parents to children, they are intended to be read-only // to avoid a child mutating its parent's state