@lwc/style-compiler 8.24.0 → 8.26.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.
@@ -1,3 +1,4 @@
1
1
  import type { Root, Result } from 'postcss';
2
- export default function process(root: Root, result: Result, isScoped: boolean): void;
2
+ import type { StyleCompilerCtx } from '../utils/error-recovery';
3
+ export default function process(root: Root, result: Result, isScoped: boolean, ctx: StyleCompilerCtx): void;
3
4
  //# sourceMappingURL=transform.d.ts.map
@@ -1,3 +1,4 @@
1
1
  import type { Root } from 'postcss-selector-parser';
2
- export default function (root: Root): void;
2
+ import type { StyleCompilerCtx } from '../utils/error-recovery';
3
+ export default function (root: Root, ctx: StyleCompilerCtx): void;
3
4
  //# sourceMappingURL=transform.d.ts.map
package/dist/index.cjs.js CHANGED
@@ -344,12 +344,14 @@ function tokenizeCss(data) {
344
344
  return tokens;
345
345
  }
346
346
 
347
- function validateIdSelectors (root) {
347
+ function validateIdSelectors (root, ctx) {
348
348
  root.walkIds((node) => {
349
- const message = `Invalid usage of id selector '#${node.value}'. Try using a class selector or some other selector.`;
350
- throw root.error(message, {
351
- index: node.sourceIndex,
352
- word: node.value,
349
+ ctx.withErrorRecovery(() => {
350
+ const message = `Invalid usage of id selector '#${node.value}'. Try using a class selector or some other selector.`;
351
+ throw root.error(message, {
352
+ index: node.sourceIndex,
353
+ word: node.value,
354
+ });
353
355
  });
354
356
  });
355
357
  }
@@ -360,36 +362,38 @@ function validateIdSelectors (root) {
360
362
  * SPDX-License-Identifier: MIT
361
363
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
362
364
  */
363
- function process$1(root, result, isScoped) {
365
+ function process$1(root, result, isScoped, ctx) {
364
366
  root.walkAtRules('import', (node) => {
365
- if (isScoped) {
366
- throw node.error(`Invalid import statement, imports are not allowed in *.scoped.css files.`);
367
- }
368
- // Ensure @import are at the top of the file
369
- let prev = node.prev();
370
- while (prev) {
371
- if (prev.type === 'comment' || (prev.type === 'atrule' && prev.name === 'import')) {
372
- prev = prev.prev();
367
+ ctx.withErrorRecovery(() => {
368
+ if (isScoped) {
369
+ throw node.error(`Invalid import statement, imports are not allowed in *.scoped.css files.`);
373
370
  }
374
- else {
375
- throw prev.error('@import must precede all other statements');
371
+ // Ensure @import are at the top of the file
372
+ let prev = node.prev();
373
+ while (prev) {
374
+ if (prev.type === 'comment' || (prev.type === 'atrule' && prev.name === 'import')) {
375
+ prev = prev.prev();
376
+ }
377
+ else {
378
+ throw prev.error('@import must precede all other statements');
379
+ }
376
380
  }
377
- }
378
- const { nodes: params } = valueParser(node.params);
379
- // Ensure import match the following syntax:
380
- // @import "foo";
381
- // @import "./foo.css";
382
- if (!params.length || params[0].type !== 'string' || !params[0].value) {
383
- throw node.error(`Invalid import statement, unable to find imported module.`);
384
- }
385
- if (params.length > 1) {
386
- throw node.error(`Invalid import statement, import statement only support a single parameter.`);
387
- }
388
- // Add the imported to results messages
389
- const message = importMessage(params[0].value);
390
- result.messages.push(message);
391
- // Remove the import from the generated css
392
- node.remove();
381
+ const { nodes: params } = valueParser(node.params);
382
+ // Ensure import match the following syntax:
383
+ // @import "foo";
384
+ // @import "./foo.css";
385
+ if (!params.length || params[0].type !== 'string' || !params[0].value) {
386
+ throw node.error(`Invalid import statement, unable to find imported module.`);
387
+ }
388
+ if (params.length > 1) {
389
+ throw node.error(`Invalid import statement, import statement only support a single parameter.`);
390
+ }
391
+ // Add the imported to results messages
392
+ const message = importMessage(params[0].value);
393
+ result.messages.push(message);
394
+ // Remove the import from the generated css
395
+ node.remove();
396
+ });
393
397
  });
394
398
  }
395
399
 
@@ -428,48 +432,52 @@ function trimNodeWhitespaces(node) {
428
432
  const DEPRECATED_SELECTORS = new Set(['/deep/', '::shadow', '>>>']);
429
433
  const UNSUPPORTED_SELECTORS = new Set([':root', ':host-context']);
430
434
  const TEMPLATE_DIRECTIVES = [/^key$/, /^lwc:*/, /^if:*/, /^for:*/, /^iterator:*/];
431
- function validateSelectors(root, native) {
435
+ function validateSelectors(root, native, ctx) {
432
436
  root.walk((node) => {
433
- const { value, sourceIndex } = node;
434
- if (value) {
435
- // Ensure the selector doesn't use a deprecated CSS selector.
436
- if (DEPRECATED_SELECTORS.has(value)) {
437
- throw root.error(`Invalid usage of deprecated selector "${value}".`, {
438
- index: sourceIndex,
439
- word: value,
440
- });
441
- }
442
- // Ensure the selector doesn't use an unsupported selector.
443
- if (!native && UNSUPPORTED_SELECTORS.has(value)) {
444
- throw root.error(`Invalid usage of unsupported selector "${value}". This selector is only supported in non-scoped CSS where the \`disableSyntheticShadowSupport\` flag is set to true.`, {
445
- index: sourceIndex,
446
- word: value,
447
- });
437
+ ctx.withErrorRecovery(() => {
438
+ const { value, sourceIndex } = node;
439
+ if (value) {
440
+ // Ensure the selector doesn't use a deprecated CSS selector.
441
+ if (DEPRECATED_SELECTORS.has(value)) {
442
+ throw root.error(`Invalid usage of deprecated selector "${value}".`, {
443
+ index: sourceIndex,
444
+ word: value,
445
+ });
446
+ }
447
+ // Ensure the selector doesn't use an unsupported selector.
448
+ if (!native && UNSUPPORTED_SELECTORS.has(value)) {
449
+ throw root.error(`Invalid usage of unsupported selector "${value}". This selector is only supported in non-scoped CSS where the \`disableSyntheticShadowSupport\` flag is set to true.`, {
450
+ index: sourceIndex,
451
+ word: value,
452
+ });
453
+ }
448
454
  }
449
- }
455
+ });
450
456
  });
451
457
  }
452
- function validateAttribute(root) {
458
+ function validateAttribute(root, ctx) {
453
459
  root.walkAttributes((node) => {
454
- const { attribute: attributeName, sourceIndex } = node;
455
- const isTemplateDirective = TEMPLATE_DIRECTIVES.some((directive) => {
456
- return directive.test(attributeName);
457
- });
458
- if (isTemplateDirective) {
459
- const message = [
460
- `Invalid usage of attribute selector "${attributeName}". `,
461
- `"${attributeName}" is a template directive and therefore not supported in css rules.`,
462
- ];
463
- throw root.error(message.join(''), {
464
- index: sourceIndex,
465
- word: attributeName,
460
+ ctx.withErrorRecovery(() => {
461
+ const { attribute: attributeName, sourceIndex } = node;
462
+ const isTemplateDirective = TEMPLATE_DIRECTIVES.some((directive) => {
463
+ return directive.test(attributeName);
466
464
  });
467
- }
465
+ if (isTemplateDirective) {
466
+ const message = [
467
+ `Invalid usage of attribute selector "${attributeName}". `,
468
+ `"${attributeName}" is a template directive and therefore not supported in css rules.`,
469
+ ];
470
+ throw root.error(message.join(''), {
471
+ index: sourceIndex,
472
+ word: attributeName,
473
+ });
474
+ }
475
+ });
468
476
  });
469
477
  }
470
- function validate(root, native) {
471
- validateSelectors(root, native);
472
- validateAttribute(root);
478
+ function validate(root, native, ctx) {
479
+ validateSelectors(root, native, ctx);
480
+ validateAttribute(root, ctx);
473
481
  }
474
482
 
475
483
  /*
@@ -578,8 +586,8 @@ function transformHost(selector) {
578
586
  replaceNodeWith(selector, ...contextualSelectors);
579
587
  }
580
588
  }
581
- function transformSelector(root, transformConfig) {
582
- validate(root, transformConfig.disableSyntheticShadowSupport && !transformConfig.scoped);
589
+ function transformSelector(root, transformConfig, ctx) {
590
+ validate(root, transformConfig.disableSyntheticShadowSupport && !transformConfig.scoped, ctx);
583
591
  root.each(scopeSelector);
584
592
  if (transformConfig.transformHost) {
585
593
  root.each(transformHost);
@@ -595,61 +603,63 @@ function transformSelector(root, transformConfig) {
595
603
  function isValidDirValue(value) {
596
604
  return value === 'ltr' || value === 'rtl';
597
605
  }
598
- function transformDirPseudoClass (root) {
606
+ function transformDirPseudoClass (root, ctx) {
599
607
  root.nodes.forEach((selector) => {
600
608
  selector.nodes.forEach((node) => {
601
- if (!isDirPseudoClass(node)) {
602
- return;
603
- }
604
- const value = node.nodes.toString().trim();
605
- if (!isValidDirValue(value)) {
606
- throw root.error(`:dir() pseudo class expects "ltr" or "rtl" for value, but received "${value}".`, {
607
- index: node.sourceIndex,
608
- word: node.value,
609
+ ctx.withErrorRecovery(() => {
610
+ if (!isDirPseudoClass(node)) {
611
+ return;
612
+ }
613
+ const value = node.nodes.toString().trim();
614
+ if (!isValidDirValue(value)) {
615
+ throw root.error(`:dir() pseudo class expects "ltr" or "rtl" for value, but received "${value}".`, {
616
+ index: node.sourceIndex,
617
+ word: node.value,
618
+ });
619
+ }
620
+ // Set placeholders for `:dir()` so we can keep it for native shadow and
621
+ // replace it with a polyfill for synthetic shadow.
622
+ //
623
+ // Native: `:dir(ltr)`
624
+ // Synthetic: `[dir="ltr"]`
625
+ //
626
+ // The placeholders look like this: `[__dirAttributeNativeLtr__]`
627
+ // The attribute has no value because it's simpler during serialization, and there
628
+ // are only two valid values: "ltr" and "rtl".
629
+ //
630
+ // Now consider a more complex selector: `.foo:dir(ltr):not(.bar)`.
631
+ // For native shadow, we need to leave it as-is. Whereas for synthetic shadow, we need
632
+ // to convert it to: `[dir="ltr"] .foo:not(.bar)`.
633
+ // I.e. we need to use a descendant selector (' ' combinator) relying on a `dir`
634
+ // attribute added to the host element. So we need two placeholders:
635
+ // `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)`
636
+ const nativeAttribute = postCssSelector.attribute({
637
+ attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL,
638
+ value: undefined,
639
+ raws: {},
609
640
  });
610
- }
611
- // Set placeholders for `:dir()` so we can keep it for native shadow and
612
- // replace it with a polyfill for synthetic shadow.
613
- //
614
- // Native: `:dir(ltr)`
615
- // Synthetic: `[dir="ltr"]`
616
- //
617
- // The placeholders look like this: `[__dirAttributeNativeLtr__]`
618
- // The attribute has no value because it's simpler during serialization, and there
619
- // are only two valid values: "ltr" and "rtl".
620
- //
621
- // Now consider a more complex selector: `.foo:dir(ltr):not(.bar)`.
622
- // For native shadow, we need to leave it as-is. Whereas for synthetic shadow, we need
623
- // to convert it to: `[dir="ltr"] .foo:not(.bar)`.
624
- // I.e. we need to use a descendant selector (' ' combinator) relying on a `dir`
625
- // attribute added to the host element. So we need two placeholders:
626
- // `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)`
627
- const nativeAttribute = postCssSelector.attribute({
628
- attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL,
629
- value: undefined,
630
- raws: {},
631
- });
632
- const syntheticAttribute = postCssSelector.attribute({
633
- attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL,
634
- value: undefined,
635
- raws: {},
641
+ const syntheticAttribute = postCssSelector.attribute({
642
+ attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL,
643
+ value: undefined,
644
+ raws: {},
645
+ });
646
+ node.replaceWith(nativeAttribute);
647
+ // If the selector is not empty and if the first node in the selector is not already a
648
+ // " " combinator, we need to use the descendant selector format
649
+ const shouldAddDescendantCombinator = selector.first && !postCssSelector.isCombinator(selector.first) && selector.first.value !== ' ';
650
+ if (shouldAddDescendantCombinator) {
651
+ selector.insertBefore(selector.first, postCssSelector.combinator({
652
+ value: ' ',
653
+ }));
654
+ // Add the [dir] attribute in front of the " " combinator, i.e. as an ancestor
655
+ selector.insertBefore(selector.first, syntheticAttribute);
656
+ }
657
+ else {
658
+ // Otherwise there's no need for the descendant selector, so we can skip adding the
659
+ // space combinator and just put the synthetic placeholder next to the native one
660
+ selector.insertBefore(nativeAttribute, syntheticAttribute);
661
+ }
636
662
  });
637
- node.replaceWith(nativeAttribute);
638
- // If the selector is not empty and if the first node in the selector is not already a
639
- // " " combinator, we need to use the descendant selector format
640
- const shouldAddDescendantCombinator = selector.first && !postCssSelector.isCombinator(selector.first) && selector.first.value !== ' ';
641
- if (shouldAddDescendantCombinator) {
642
- selector.insertBefore(selector.first, postCssSelector.combinator({
643
- value: ' ',
644
- }));
645
- // Add the [dir] attribute in front of the " " combinator, i.e. as an ancestor
646
- selector.insertBefore(selector.first, syntheticAttribute);
647
- }
648
- else {
649
- // Otherwise there's no need for the descendant selector, so we can skip adding the
650
- // space combinator and just put the synthetic placeholder next to the native one
651
- selector.insertBefore(nativeAttribute, syntheticAttribute);
652
- }
653
663
  });
654
664
  });
655
665
  }
@@ -676,35 +686,39 @@ function getAllNames(name) {
676
686
  }
677
687
  const ANIMATION = getAllNames('animation');
678
688
  const ANIMATION_NAME = getAllNames('animation-name');
679
- function process(root) {
689
+ function process(root, ctx) {
680
690
  const knownNames = new Set();
681
691
  root.walkAtRules((atRule) => {
682
- // Note that @-webkit-keyframes, @-moz-keyframes, etc. are not actually a thing supported
683
- // in any browser, even though you'll see it on some StackOverflow answers.
684
- if (atRule.name === 'keyframes') {
685
- const { params } = atRule;
686
- knownNames.add(params);
687
- atRule.params = `${params}-${SHADOW_ATTRIBUTE}`;
688
- }
692
+ ctx.withErrorRecovery(() => {
693
+ // Note that @-webkit-keyframes, @-moz-keyframes, etc. are not actually a thing supported
694
+ // in any browser, even though you'll see it on some StackOverflow answers.
695
+ if (atRule.name === 'keyframes') {
696
+ const { params } = atRule;
697
+ knownNames.add(params);
698
+ atRule.params = `${params}-${SHADOW_ATTRIBUTE}`;
699
+ }
700
+ });
689
701
  });
690
702
  root.walkRules((rule) => {
691
703
  rule.walkDecls((decl) => {
692
- if (ANIMATION.has(decl.prop)) {
693
- // Use a simple heuristic of breaking up the tokens by whitespace. We could use
694
- // a dedicated animation prop parser (e.g.
695
- // https://github.com/hookhookun/parse-animation-shorthand) but it's
696
- // probably overkill.
697
- const tokens = decl.value
698
- .trim()
699
- .split(/\s+/g)
700
- .map((token) => knownNames.has(token) ? `${token}-${SHADOW_ATTRIBUTE}` : token);
701
- decl.value = tokens.join(' ');
702
- }
703
- else if (ANIMATION_NAME.has(decl.prop)) {
704
- if (knownNames.has(decl.value)) {
705
- decl.value = `${decl.value}-${SHADOW_ATTRIBUTE}`;
704
+ ctx.withErrorRecovery(() => {
705
+ if (ANIMATION.has(decl.prop)) {
706
+ // Use a simple heuristic of breaking up the tokens by whitespace. We could use
707
+ // a dedicated animation prop parser (e.g.
708
+ // https://github.com/hookhookun/parse-animation-shorthand) but it's
709
+ // probably overkill.
710
+ const tokens = decl.value
711
+ .trim()
712
+ .split(/\s+/g)
713
+ .map((token) => knownNames.has(token) ? `${token}-${SHADOW_ATTRIBUTE}` : token);
714
+ decl.value = tokens.join(' ');
706
715
  }
707
- }
716
+ else if (ANIMATION_NAME.has(decl.prop)) {
717
+ if (knownNames.has(decl.value)) {
718
+ decl.value = `${decl.value}-${SHADOW_ATTRIBUTE}`;
719
+ }
720
+ }
721
+ });
708
722
  });
709
723
  });
710
724
  }
@@ -720,14 +734,15 @@ function shouldTransformSelector(rule) {
720
734
  // scoped like any other rules.
721
735
  return rule.parent?.type !== 'atrule' || rule.parent.name !== 'keyframes';
722
736
  }
723
- function selectorProcessorFactory(transformConfig) {
737
+ function selectorProcessorFactory(transformConfig, ctx) {
724
738
  return postCssSelector((root) => {
725
- validateIdSelectors(root);
726
- transformSelector(root, transformConfig);
727
- transformDirPseudoClass(root);
739
+ validateIdSelectors(root, ctx);
740
+ transformSelector(root, transformConfig, ctx);
741
+ transformDirPseudoClass(root, ctx);
728
742
  });
729
743
  }
730
744
  function postCssLwcPlugin(options) {
745
+ const { ctx } = options;
731
746
  // We need 2 types of selectors processors, since transforming the :host selector make the selector
732
747
  // unusable when used in the context of the native shadow and vice-versa.
733
748
  // This distinction also applies to light DOM in scoped (synthetic-like) vs unscoped (native-like) mode.
@@ -735,39 +750,86 @@ function postCssLwcPlugin(options) {
735
750
  transformHost: false,
736
751
  disableSyntheticShadowSupport: options.disableSyntheticShadowSupport,
737
752
  scoped: options.scoped,
738
- });
753
+ }, ctx);
739
754
  const syntheticShadowSelectorProcessor = selectorProcessorFactory({
740
755
  transformHost: true,
741
756
  disableSyntheticShadowSupport: options.disableSyntheticShadowSupport,
742
757
  scoped: options.scoped,
743
- });
758
+ }, ctx);
744
759
  return (root, result) => {
745
- process$1(root, result, options.scoped);
746
- process(root);
760
+ process$1(root, result, options.scoped, ctx);
761
+ process(root, ctx);
762
+ // Wrap rule processing with error recovery
747
763
  root.walkRules((rule) => {
748
- if (!shouldTransformSelector(rule)) {
749
- return;
750
- }
751
- // Let transform the selector with the 2 processors.
752
- const syntheticSelector = syntheticShadowSelectorProcessor.processSync(rule);
753
- const nativeSelector = nativeShadowSelectorProcessor.processSync(rule);
754
- rule.selector = syntheticSelector;
755
- // If the resulting selector are different it means that the selector use the :host selector. In
756
- // this case we need to duplicate the CSS rule and assign the other selector.
757
- if (syntheticSelector !== nativeSelector) {
758
- // The cloned selector is inserted before the currently processed selector to avoid processing
759
- // again the cloned selector.
760
- const currentRule = rule;
761
- const clonedRule = rule.cloneBefore();
762
- clonedRule.selector = nativeSelector;
763
- // Safe a reference to each other
764
- clonedRule._isNativeHost = true;
765
- currentRule._isSyntheticHost = true;
766
- }
764
+ ctx.withErrorRecovery(() => {
765
+ if (!shouldTransformSelector(rule)) {
766
+ return;
767
+ }
768
+ // Let transform the selector with the 2 processors.
769
+ const syntheticSelector = syntheticShadowSelectorProcessor.processSync(rule);
770
+ const nativeSelector = nativeShadowSelectorProcessor.processSync(rule);
771
+ rule.selector = syntheticSelector;
772
+ // If the resulting selector are different it means that the selector use the :host selector. In
773
+ // this case we need to duplicate the CSS rule and assign the other selector.
774
+ if (syntheticSelector !== nativeSelector) {
775
+ // The cloned selector is inserted before the currently processed selector to avoid processing
776
+ // again the cloned selector.
777
+ const currentRule = rule;
778
+ const clonedRule = rule.cloneBefore();
779
+ clonedRule.selector = nativeSelector;
780
+ // Safe a reference to each other
781
+ clonedRule._isNativeHost = true;
782
+ currentRule._isSyntheticHost = true;
783
+ }
784
+ });
767
785
  });
768
786
  };
769
787
  }
770
788
 
789
+ /*
790
+ * Copyright (c) 2025, Salesforce, Inc.
791
+ * All rights reserved.
792
+ * SPDX-License-Identifier: MIT
793
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
794
+ */
795
+ class StyleCompilerCtx {
796
+ constructor(errorRecoveryMode, filename) {
797
+ this.errors = [];
798
+ this.seenErrorKeys = new Set();
799
+ this.errorRecoveryMode = errorRecoveryMode;
800
+ this.filename = filename;
801
+ }
802
+ /**
803
+ * This method recovers from CSS syntax errors that are encountered when fn is invoked.
804
+ * All other errors are considered compiler errors and can not be recovered from.
805
+ * @param fn method to be invoked.
806
+ */
807
+ withErrorRecovery(fn) {
808
+ if (!this.errorRecoveryMode) {
809
+ return fn();
810
+ }
811
+ try {
812
+ return fn();
813
+ }
814
+ catch (error) {
815
+ if (error instanceof postcss.CssSyntaxError) {
816
+ if (this.seenErrorKeys.has(error.message)) {
817
+ return;
818
+ }
819
+ this.seenErrorKeys.add(error.message);
820
+ this.errors.push(error);
821
+ }
822
+ else {
823
+ // Non-CSS errors (compiler errors) should still throw
824
+ throw error;
825
+ }
826
+ }
827
+ }
828
+ hasErrors() {
829
+ return this.errors.length > 0;
830
+ }
831
+ }
832
+
771
833
  /*
772
834
  * Copyright (c) 2024, Salesforce, Inc.
773
835
  * All rights reserved.
@@ -798,11 +860,36 @@ function transform(src, id, config = {}) {
798
860
  const scoped = !!config.scoped;
799
861
  shared.getAPIVersionFromNumber(config.apiVersion);
800
862
  const disableSyntheticShadowSupport = !!config.disableSyntheticShadowSupport;
801
- const plugins = [postCssLwcPlugin({ scoped, disableSyntheticShadowSupport })];
802
- const result = postcss(plugins).process(src, { from: id }).sync();
863
+ const errorRecoveryMode = !!config.experimentalErrorRecoveryMode;
864
+ // Create error recovery context
865
+ const ctx = new StyleCompilerCtx(errorRecoveryMode, id);
866
+ const plugins = [
867
+ postCssLwcPlugin({
868
+ scoped,
869
+ disableSyntheticShadowSupport,
870
+ ctx,
871
+ }),
872
+ ];
873
+ // Wrap PostCSS processing with error recovery for parsing errors
874
+ let result;
875
+ try {
876
+ result = postcss(plugins).process(src, { from: id }).sync();
877
+ }
878
+ catch (error) {
879
+ if (errorRecoveryMode && error instanceof postcss.CssSyntaxError) {
880
+ ctx.errors.push(error);
881
+ throw AggregateError(ctx.errors);
882
+ }
883
+ else {
884
+ throw error;
885
+ }
886
+ }
887
+ if (errorRecoveryMode && ctx.hasErrors()) {
888
+ throw AggregateError(ctx.errors);
889
+ }
803
890
  return { code: serialize(result, config) };
804
891
  }
805
892
 
806
893
  exports.transform = transform;
807
- /** version: 8.24.0 */
894
+ /** version: 8.26.0 */
808
895
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Copyright (c) 2025 Salesforce, Inc.
3
3
  */
4
- import postcss from 'postcss';
4
+ import postcss, { CssSyntaxError } from 'postcss';
5
5
  import { LWC_VERSION_COMMENT, KEY__SCOPED_CSS, KEY__NATIVE_ONLY_CSS, getAPIVersionFromNumber } from '@lwc/shared';
6
6
  import postCssSelector, { isPseudoClass, isCombinator, isPseudoElement, attribute, combinator } from 'postcss-selector-parser';
7
7
  import valueParser from 'postcss-value-parser';
@@ -340,12 +340,14 @@ function tokenizeCss(data) {
340
340
  return tokens;
341
341
  }
342
342
 
343
- function validateIdSelectors (root) {
343
+ function validateIdSelectors (root, ctx) {
344
344
  root.walkIds((node) => {
345
- const message = `Invalid usage of id selector '#${node.value}'. Try using a class selector or some other selector.`;
346
- throw root.error(message, {
347
- index: node.sourceIndex,
348
- word: node.value,
345
+ ctx.withErrorRecovery(() => {
346
+ const message = `Invalid usage of id selector '#${node.value}'. Try using a class selector or some other selector.`;
347
+ throw root.error(message, {
348
+ index: node.sourceIndex,
349
+ word: node.value,
350
+ });
349
351
  });
350
352
  });
351
353
  }
@@ -356,36 +358,38 @@ function validateIdSelectors (root) {
356
358
  * SPDX-License-Identifier: MIT
357
359
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
358
360
  */
359
- function process$1(root, result, isScoped) {
361
+ function process$1(root, result, isScoped, ctx) {
360
362
  root.walkAtRules('import', (node) => {
361
- if (isScoped) {
362
- throw node.error(`Invalid import statement, imports are not allowed in *.scoped.css files.`);
363
- }
364
- // Ensure @import are at the top of the file
365
- let prev = node.prev();
366
- while (prev) {
367
- if (prev.type === 'comment' || (prev.type === 'atrule' && prev.name === 'import')) {
368
- prev = prev.prev();
363
+ ctx.withErrorRecovery(() => {
364
+ if (isScoped) {
365
+ throw node.error(`Invalid import statement, imports are not allowed in *.scoped.css files.`);
369
366
  }
370
- else {
371
- throw prev.error('@import must precede all other statements');
367
+ // Ensure @import are at the top of the file
368
+ let prev = node.prev();
369
+ while (prev) {
370
+ if (prev.type === 'comment' || (prev.type === 'atrule' && prev.name === 'import')) {
371
+ prev = prev.prev();
372
+ }
373
+ else {
374
+ throw prev.error('@import must precede all other statements');
375
+ }
372
376
  }
373
- }
374
- const { nodes: params } = valueParser(node.params);
375
- // Ensure import match the following syntax:
376
- // @import "foo";
377
- // @import "./foo.css";
378
- if (!params.length || params[0].type !== 'string' || !params[0].value) {
379
- throw node.error(`Invalid import statement, unable to find imported module.`);
380
- }
381
- if (params.length > 1) {
382
- throw node.error(`Invalid import statement, import statement only support a single parameter.`);
383
- }
384
- // Add the imported to results messages
385
- const message = importMessage(params[0].value);
386
- result.messages.push(message);
387
- // Remove the import from the generated css
388
- node.remove();
377
+ const { nodes: params } = valueParser(node.params);
378
+ // Ensure import match the following syntax:
379
+ // @import "foo";
380
+ // @import "./foo.css";
381
+ if (!params.length || params[0].type !== 'string' || !params[0].value) {
382
+ throw node.error(`Invalid import statement, unable to find imported module.`);
383
+ }
384
+ if (params.length > 1) {
385
+ throw node.error(`Invalid import statement, import statement only support a single parameter.`);
386
+ }
387
+ // Add the imported to results messages
388
+ const message = importMessage(params[0].value);
389
+ result.messages.push(message);
390
+ // Remove the import from the generated css
391
+ node.remove();
392
+ });
389
393
  });
390
394
  }
391
395
 
@@ -424,48 +428,52 @@ function trimNodeWhitespaces(node) {
424
428
  const DEPRECATED_SELECTORS = new Set(['/deep/', '::shadow', '>>>']);
425
429
  const UNSUPPORTED_SELECTORS = new Set([':root', ':host-context']);
426
430
  const TEMPLATE_DIRECTIVES = [/^key$/, /^lwc:*/, /^if:*/, /^for:*/, /^iterator:*/];
427
- function validateSelectors(root, native) {
431
+ function validateSelectors(root, native, ctx) {
428
432
  root.walk((node) => {
429
- const { value, sourceIndex } = node;
430
- if (value) {
431
- // Ensure the selector doesn't use a deprecated CSS selector.
432
- if (DEPRECATED_SELECTORS.has(value)) {
433
- throw root.error(`Invalid usage of deprecated selector "${value}".`, {
434
- index: sourceIndex,
435
- word: value,
436
- });
437
- }
438
- // Ensure the selector doesn't use an unsupported selector.
439
- if (!native && UNSUPPORTED_SELECTORS.has(value)) {
440
- throw root.error(`Invalid usage of unsupported selector "${value}". This selector is only supported in non-scoped CSS where the \`disableSyntheticShadowSupport\` flag is set to true.`, {
441
- index: sourceIndex,
442
- word: value,
443
- });
433
+ ctx.withErrorRecovery(() => {
434
+ const { value, sourceIndex } = node;
435
+ if (value) {
436
+ // Ensure the selector doesn't use a deprecated CSS selector.
437
+ if (DEPRECATED_SELECTORS.has(value)) {
438
+ throw root.error(`Invalid usage of deprecated selector "${value}".`, {
439
+ index: sourceIndex,
440
+ word: value,
441
+ });
442
+ }
443
+ // Ensure the selector doesn't use an unsupported selector.
444
+ if (!native && UNSUPPORTED_SELECTORS.has(value)) {
445
+ throw root.error(`Invalid usage of unsupported selector "${value}". This selector is only supported in non-scoped CSS where the \`disableSyntheticShadowSupport\` flag is set to true.`, {
446
+ index: sourceIndex,
447
+ word: value,
448
+ });
449
+ }
444
450
  }
445
- }
451
+ });
446
452
  });
447
453
  }
448
- function validateAttribute(root) {
454
+ function validateAttribute(root, ctx) {
449
455
  root.walkAttributes((node) => {
450
- const { attribute: attributeName, sourceIndex } = node;
451
- const isTemplateDirective = TEMPLATE_DIRECTIVES.some((directive) => {
452
- return directive.test(attributeName);
453
- });
454
- if (isTemplateDirective) {
455
- const message = [
456
- `Invalid usage of attribute selector "${attributeName}". `,
457
- `"${attributeName}" is a template directive and therefore not supported in css rules.`,
458
- ];
459
- throw root.error(message.join(''), {
460
- index: sourceIndex,
461
- word: attributeName,
456
+ ctx.withErrorRecovery(() => {
457
+ const { attribute: attributeName, sourceIndex } = node;
458
+ const isTemplateDirective = TEMPLATE_DIRECTIVES.some((directive) => {
459
+ return directive.test(attributeName);
462
460
  });
463
- }
461
+ if (isTemplateDirective) {
462
+ const message = [
463
+ `Invalid usage of attribute selector "${attributeName}". `,
464
+ `"${attributeName}" is a template directive and therefore not supported in css rules.`,
465
+ ];
466
+ throw root.error(message.join(''), {
467
+ index: sourceIndex,
468
+ word: attributeName,
469
+ });
470
+ }
471
+ });
464
472
  });
465
473
  }
466
- function validate(root, native) {
467
- validateSelectors(root, native);
468
- validateAttribute(root);
474
+ function validate(root, native, ctx) {
475
+ validateSelectors(root, native, ctx);
476
+ validateAttribute(root, ctx);
469
477
  }
470
478
 
471
479
  /*
@@ -574,8 +582,8 @@ function transformHost(selector) {
574
582
  replaceNodeWith(selector, ...contextualSelectors);
575
583
  }
576
584
  }
577
- function transformSelector(root, transformConfig) {
578
- validate(root, transformConfig.disableSyntheticShadowSupport && !transformConfig.scoped);
585
+ function transformSelector(root, transformConfig, ctx) {
586
+ validate(root, transformConfig.disableSyntheticShadowSupport && !transformConfig.scoped, ctx);
579
587
  root.each(scopeSelector);
580
588
  if (transformConfig.transformHost) {
581
589
  root.each(transformHost);
@@ -591,61 +599,63 @@ function transformSelector(root, transformConfig) {
591
599
  function isValidDirValue(value) {
592
600
  return value === 'ltr' || value === 'rtl';
593
601
  }
594
- function transformDirPseudoClass (root) {
602
+ function transformDirPseudoClass (root, ctx) {
595
603
  root.nodes.forEach((selector) => {
596
604
  selector.nodes.forEach((node) => {
597
- if (!isDirPseudoClass(node)) {
598
- return;
599
- }
600
- const value = node.nodes.toString().trim();
601
- if (!isValidDirValue(value)) {
602
- throw root.error(`:dir() pseudo class expects "ltr" or "rtl" for value, but received "${value}".`, {
603
- index: node.sourceIndex,
604
- word: node.value,
605
+ ctx.withErrorRecovery(() => {
606
+ if (!isDirPseudoClass(node)) {
607
+ return;
608
+ }
609
+ const value = node.nodes.toString().trim();
610
+ if (!isValidDirValue(value)) {
611
+ throw root.error(`:dir() pseudo class expects "ltr" or "rtl" for value, but received "${value}".`, {
612
+ index: node.sourceIndex,
613
+ word: node.value,
614
+ });
615
+ }
616
+ // Set placeholders for `:dir()` so we can keep it for native shadow and
617
+ // replace it with a polyfill for synthetic shadow.
618
+ //
619
+ // Native: `:dir(ltr)`
620
+ // Synthetic: `[dir="ltr"]`
621
+ //
622
+ // The placeholders look like this: `[__dirAttributeNativeLtr__]`
623
+ // The attribute has no value because it's simpler during serialization, and there
624
+ // are only two valid values: "ltr" and "rtl".
625
+ //
626
+ // Now consider a more complex selector: `.foo:dir(ltr):not(.bar)`.
627
+ // For native shadow, we need to leave it as-is. Whereas for synthetic shadow, we need
628
+ // to convert it to: `[dir="ltr"] .foo:not(.bar)`.
629
+ // I.e. we need to use a descendant selector (' ' combinator) relying on a `dir`
630
+ // attribute added to the host element. So we need two placeholders:
631
+ // `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)`
632
+ const nativeAttribute = attribute({
633
+ attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL,
634
+ value: undefined,
635
+ raws: {},
605
636
  });
606
- }
607
- // Set placeholders for `:dir()` so we can keep it for native shadow and
608
- // replace it with a polyfill for synthetic shadow.
609
- //
610
- // Native: `:dir(ltr)`
611
- // Synthetic: `[dir="ltr"]`
612
- //
613
- // The placeholders look like this: `[__dirAttributeNativeLtr__]`
614
- // The attribute has no value because it's simpler during serialization, and there
615
- // are only two valid values: "ltr" and "rtl".
616
- //
617
- // Now consider a more complex selector: `.foo:dir(ltr):not(.bar)`.
618
- // For native shadow, we need to leave it as-is. Whereas for synthetic shadow, we need
619
- // to convert it to: `[dir="ltr"] .foo:not(.bar)`.
620
- // I.e. we need to use a descendant selector (' ' combinator) relying on a `dir`
621
- // attribute added to the host element. So we need two placeholders:
622
- // `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)`
623
- const nativeAttribute = attribute({
624
- attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL,
625
- value: undefined,
626
- raws: {},
627
- });
628
- const syntheticAttribute = attribute({
629
- attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL,
630
- value: undefined,
631
- raws: {},
637
+ const syntheticAttribute = attribute({
638
+ attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL,
639
+ value: undefined,
640
+ raws: {},
641
+ });
642
+ node.replaceWith(nativeAttribute);
643
+ // If the selector is not empty and if the first node in the selector is not already a
644
+ // " " combinator, we need to use the descendant selector format
645
+ const shouldAddDescendantCombinator = selector.first && !isCombinator(selector.first) && selector.first.value !== ' ';
646
+ if (shouldAddDescendantCombinator) {
647
+ selector.insertBefore(selector.first, combinator({
648
+ value: ' ',
649
+ }));
650
+ // Add the [dir] attribute in front of the " " combinator, i.e. as an ancestor
651
+ selector.insertBefore(selector.first, syntheticAttribute);
652
+ }
653
+ else {
654
+ // Otherwise there's no need for the descendant selector, so we can skip adding the
655
+ // space combinator and just put the synthetic placeholder next to the native one
656
+ selector.insertBefore(nativeAttribute, syntheticAttribute);
657
+ }
632
658
  });
633
- node.replaceWith(nativeAttribute);
634
- // If the selector is not empty and if the first node in the selector is not already a
635
- // " " combinator, we need to use the descendant selector format
636
- const shouldAddDescendantCombinator = selector.first && !isCombinator(selector.first) && selector.first.value !== ' ';
637
- if (shouldAddDescendantCombinator) {
638
- selector.insertBefore(selector.first, combinator({
639
- value: ' ',
640
- }));
641
- // Add the [dir] attribute in front of the " " combinator, i.e. as an ancestor
642
- selector.insertBefore(selector.first, syntheticAttribute);
643
- }
644
- else {
645
- // Otherwise there's no need for the descendant selector, so we can skip adding the
646
- // space combinator and just put the synthetic placeholder next to the native one
647
- selector.insertBefore(nativeAttribute, syntheticAttribute);
648
- }
649
659
  });
650
660
  });
651
661
  }
@@ -672,35 +682,39 @@ function getAllNames(name) {
672
682
  }
673
683
  const ANIMATION = getAllNames('animation');
674
684
  const ANIMATION_NAME = getAllNames('animation-name');
675
- function process(root) {
685
+ function process(root, ctx) {
676
686
  const knownNames = new Set();
677
687
  root.walkAtRules((atRule) => {
678
- // Note that @-webkit-keyframes, @-moz-keyframes, etc. are not actually a thing supported
679
- // in any browser, even though you'll see it on some StackOverflow answers.
680
- if (atRule.name === 'keyframes') {
681
- const { params } = atRule;
682
- knownNames.add(params);
683
- atRule.params = `${params}-${SHADOW_ATTRIBUTE}`;
684
- }
688
+ ctx.withErrorRecovery(() => {
689
+ // Note that @-webkit-keyframes, @-moz-keyframes, etc. are not actually a thing supported
690
+ // in any browser, even though you'll see it on some StackOverflow answers.
691
+ if (atRule.name === 'keyframes') {
692
+ const { params } = atRule;
693
+ knownNames.add(params);
694
+ atRule.params = `${params}-${SHADOW_ATTRIBUTE}`;
695
+ }
696
+ });
685
697
  });
686
698
  root.walkRules((rule) => {
687
699
  rule.walkDecls((decl) => {
688
- if (ANIMATION.has(decl.prop)) {
689
- // Use a simple heuristic of breaking up the tokens by whitespace. We could use
690
- // a dedicated animation prop parser (e.g.
691
- // https://github.com/hookhookun/parse-animation-shorthand) but it's
692
- // probably overkill.
693
- const tokens = decl.value
694
- .trim()
695
- .split(/\s+/g)
696
- .map((token) => knownNames.has(token) ? `${token}-${SHADOW_ATTRIBUTE}` : token);
697
- decl.value = tokens.join(' ');
698
- }
699
- else if (ANIMATION_NAME.has(decl.prop)) {
700
- if (knownNames.has(decl.value)) {
701
- decl.value = `${decl.value}-${SHADOW_ATTRIBUTE}`;
700
+ ctx.withErrorRecovery(() => {
701
+ if (ANIMATION.has(decl.prop)) {
702
+ // Use a simple heuristic of breaking up the tokens by whitespace. We could use
703
+ // a dedicated animation prop parser (e.g.
704
+ // https://github.com/hookhookun/parse-animation-shorthand) but it's
705
+ // probably overkill.
706
+ const tokens = decl.value
707
+ .trim()
708
+ .split(/\s+/g)
709
+ .map((token) => knownNames.has(token) ? `${token}-${SHADOW_ATTRIBUTE}` : token);
710
+ decl.value = tokens.join(' ');
702
711
  }
703
- }
712
+ else if (ANIMATION_NAME.has(decl.prop)) {
713
+ if (knownNames.has(decl.value)) {
714
+ decl.value = `${decl.value}-${SHADOW_ATTRIBUTE}`;
715
+ }
716
+ }
717
+ });
704
718
  });
705
719
  });
706
720
  }
@@ -716,14 +730,15 @@ function shouldTransformSelector(rule) {
716
730
  // scoped like any other rules.
717
731
  return rule.parent?.type !== 'atrule' || rule.parent.name !== 'keyframes';
718
732
  }
719
- function selectorProcessorFactory(transformConfig) {
733
+ function selectorProcessorFactory(transformConfig, ctx) {
720
734
  return postCssSelector((root) => {
721
- validateIdSelectors(root);
722
- transformSelector(root, transformConfig);
723
- transformDirPseudoClass(root);
735
+ validateIdSelectors(root, ctx);
736
+ transformSelector(root, transformConfig, ctx);
737
+ transformDirPseudoClass(root, ctx);
724
738
  });
725
739
  }
726
740
  function postCssLwcPlugin(options) {
741
+ const { ctx } = options;
727
742
  // We need 2 types of selectors processors, since transforming the :host selector make the selector
728
743
  // unusable when used in the context of the native shadow and vice-versa.
729
744
  // This distinction also applies to light DOM in scoped (synthetic-like) vs unscoped (native-like) mode.
@@ -731,39 +746,86 @@ function postCssLwcPlugin(options) {
731
746
  transformHost: false,
732
747
  disableSyntheticShadowSupport: options.disableSyntheticShadowSupport,
733
748
  scoped: options.scoped,
734
- });
749
+ }, ctx);
735
750
  const syntheticShadowSelectorProcessor = selectorProcessorFactory({
736
751
  transformHost: true,
737
752
  disableSyntheticShadowSupport: options.disableSyntheticShadowSupport,
738
753
  scoped: options.scoped,
739
- });
754
+ }, ctx);
740
755
  return (root, result) => {
741
- process$1(root, result, options.scoped);
742
- process(root);
756
+ process$1(root, result, options.scoped, ctx);
757
+ process(root, ctx);
758
+ // Wrap rule processing with error recovery
743
759
  root.walkRules((rule) => {
744
- if (!shouldTransformSelector(rule)) {
745
- return;
746
- }
747
- // Let transform the selector with the 2 processors.
748
- const syntheticSelector = syntheticShadowSelectorProcessor.processSync(rule);
749
- const nativeSelector = nativeShadowSelectorProcessor.processSync(rule);
750
- rule.selector = syntheticSelector;
751
- // If the resulting selector are different it means that the selector use the :host selector. In
752
- // this case we need to duplicate the CSS rule and assign the other selector.
753
- if (syntheticSelector !== nativeSelector) {
754
- // The cloned selector is inserted before the currently processed selector to avoid processing
755
- // again the cloned selector.
756
- const currentRule = rule;
757
- const clonedRule = rule.cloneBefore();
758
- clonedRule.selector = nativeSelector;
759
- // Safe a reference to each other
760
- clonedRule._isNativeHost = true;
761
- currentRule._isSyntheticHost = true;
762
- }
760
+ ctx.withErrorRecovery(() => {
761
+ if (!shouldTransformSelector(rule)) {
762
+ return;
763
+ }
764
+ // Let transform the selector with the 2 processors.
765
+ const syntheticSelector = syntheticShadowSelectorProcessor.processSync(rule);
766
+ const nativeSelector = nativeShadowSelectorProcessor.processSync(rule);
767
+ rule.selector = syntheticSelector;
768
+ // If the resulting selector are different it means that the selector use the :host selector. In
769
+ // this case we need to duplicate the CSS rule and assign the other selector.
770
+ if (syntheticSelector !== nativeSelector) {
771
+ // The cloned selector is inserted before the currently processed selector to avoid processing
772
+ // again the cloned selector.
773
+ const currentRule = rule;
774
+ const clonedRule = rule.cloneBefore();
775
+ clonedRule.selector = nativeSelector;
776
+ // Safe a reference to each other
777
+ clonedRule._isNativeHost = true;
778
+ currentRule._isSyntheticHost = true;
779
+ }
780
+ });
763
781
  });
764
782
  };
765
783
  }
766
784
 
785
+ /*
786
+ * Copyright (c) 2025, Salesforce, Inc.
787
+ * All rights reserved.
788
+ * SPDX-License-Identifier: MIT
789
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
790
+ */
791
+ class StyleCompilerCtx {
792
+ constructor(errorRecoveryMode, filename) {
793
+ this.errors = [];
794
+ this.seenErrorKeys = new Set();
795
+ this.errorRecoveryMode = errorRecoveryMode;
796
+ this.filename = filename;
797
+ }
798
+ /**
799
+ * This method recovers from CSS syntax errors that are encountered when fn is invoked.
800
+ * All other errors are considered compiler errors and can not be recovered from.
801
+ * @param fn method to be invoked.
802
+ */
803
+ withErrorRecovery(fn) {
804
+ if (!this.errorRecoveryMode) {
805
+ return fn();
806
+ }
807
+ try {
808
+ return fn();
809
+ }
810
+ catch (error) {
811
+ if (error instanceof CssSyntaxError) {
812
+ if (this.seenErrorKeys.has(error.message)) {
813
+ return;
814
+ }
815
+ this.seenErrorKeys.add(error.message);
816
+ this.errors.push(error);
817
+ }
818
+ else {
819
+ // Non-CSS errors (compiler errors) should still throw
820
+ throw error;
821
+ }
822
+ }
823
+ }
824
+ hasErrors() {
825
+ return this.errors.length > 0;
826
+ }
827
+ }
828
+
767
829
  /*
768
830
  * Copyright (c) 2024, Salesforce, Inc.
769
831
  * All rights reserved.
@@ -794,11 +856,36 @@ function transform(src, id, config = {}) {
794
856
  const scoped = !!config.scoped;
795
857
  getAPIVersionFromNumber(config.apiVersion);
796
858
  const disableSyntheticShadowSupport = !!config.disableSyntheticShadowSupport;
797
- const plugins = [postCssLwcPlugin({ scoped, disableSyntheticShadowSupport })];
798
- const result = postcss(plugins).process(src, { from: id }).sync();
859
+ const errorRecoveryMode = !!config.experimentalErrorRecoveryMode;
860
+ // Create error recovery context
861
+ const ctx = new StyleCompilerCtx(errorRecoveryMode, id);
862
+ const plugins = [
863
+ postCssLwcPlugin({
864
+ scoped,
865
+ disableSyntheticShadowSupport,
866
+ ctx,
867
+ }),
868
+ ];
869
+ // Wrap PostCSS processing with error recovery for parsing errors
870
+ let result;
871
+ try {
872
+ result = postcss(plugins).process(src, { from: id }).sync();
873
+ }
874
+ catch (error) {
875
+ if (errorRecoveryMode && error instanceof postcss.CssSyntaxError) {
876
+ ctx.errors.push(error);
877
+ throw AggregateError(ctx.errors);
878
+ }
879
+ else {
880
+ throw error;
881
+ }
882
+ }
883
+ if (errorRecoveryMode && ctx.hasErrors()) {
884
+ throw AggregateError(ctx.errors);
885
+ }
799
886
  return { code: serialize(result, config) };
800
887
  }
801
888
 
802
889
  export { transform };
803
- /** version: 8.24.0 */
890
+ /** version: 8.26.0 */
804
891
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,4 @@
1
1
  import type { Root } from 'postcss-selector-parser';
2
- export default function (root: Root): void;
2
+ import type { StyleCompilerCtx } from '../utils/error-recovery';
3
+ export default function (root: Root, ctx: StyleCompilerCtx): void;
3
4
  //# sourceMappingURL=validate.d.ts.map
@@ -1,8 +1,10 @@
1
1
  import type { APIVersion } from '@lwc/shared';
2
2
  import type { TransformCallback } from 'postcss';
3
+ import type { StyleCompilerCtx } from './utils/error-recovery';
3
4
  export default function postCssLwcPlugin(options: {
4
5
  scoped: boolean;
5
6
  apiVersion: APIVersion;
6
7
  disableSyntheticShadowSupport: boolean;
8
+ ctx: StyleCompilerCtx;
7
9
  }): TransformCallback;
8
10
  //# sourceMappingURL=postcss-lwc-plugin.d.ts.map
@@ -1,3 +1,4 @@
1
1
  import type { Root } from 'postcss';
2
- export default function process(root: Root): void;
2
+ import type { StyleCompilerCtx } from '../utils/error-recovery';
3
+ export default function process(root: Root, ctx: StyleCompilerCtx): void;
3
4
  //# sourceMappingURL=transform.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import type { Root } from 'postcss-selector-parser';
2
+ import type { StyleCompilerCtx } from '../utils/error-recovery';
2
3
  export interface SelectorScopingConfig {
3
4
  /** When set to true, the :host selector gets replace with the the scoping token. */
4
5
  transformHost: boolean;
@@ -7,5 +8,5 @@ export interface SelectorScopingConfig {
7
8
  /** When set to true, the selector is scoped. */
8
9
  scoped: boolean;
9
10
  }
10
- export default function transformSelector(root: Root, transformConfig: SelectorScopingConfig): void;
11
+ export default function transformSelector(root: Root, transformConfig: SelectorScopingConfig, ctx: StyleCompilerCtx): void;
11
12
  //# sourceMappingURL=transform.d.ts.map
@@ -1,3 +1,4 @@
1
1
  import type { Root } from 'postcss-selector-parser';
2
- export default function validate(root: Root, native: boolean): void;
2
+ import type { StyleCompilerCtx } from '../utils/error-recovery';
3
+ export default function validate(root: Root, native: boolean, ctx: StyleCompilerCtx): void;
3
4
  //# sourceMappingURL=validate.d.ts.map
@@ -14,6 +14,8 @@ export interface Config {
14
14
  disableSyntheticShadowSupport?: boolean;
15
15
  /** The API version to associate with the compiled stylesheet */
16
16
  apiVersion?: number;
17
+ /** When set to true, enables error recovery mode to collect multiple errors */
18
+ experimentalErrorRecoveryMode?: boolean;
17
19
  }
18
20
  /**
19
21
  * Transforms CSS for use with LWC components.
@@ -34,5 +36,6 @@ export interface Config {
34
36
  */
35
37
  export declare function transform(src: string, id: string, config?: Config): {
36
38
  code: string;
39
+ errors?: Error[];
37
40
  };
38
41
  //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1,16 @@
1
+ import { CssSyntaxError } from 'postcss';
2
+ export declare class StyleCompilerCtx {
3
+ readonly errorRecoveryMode: boolean;
4
+ readonly errors: CssSyntaxError[];
5
+ readonly filename: string;
6
+ private readonly seenErrorKeys;
7
+ constructor(errorRecoveryMode: boolean, filename: string);
8
+ /**
9
+ * This method recovers from CSS syntax errors that are encountered when fn is invoked.
10
+ * All other errors are considered compiler errors and can not be recovered from.
11
+ * @param fn method to be invoked.
12
+ */
13
+ withErrorRecovery<T>(fn: () => T): T | undefined;
14
+ hasErrors(): boolean;
15
+ }
16
+ //# sourceMappingURL=error-recovery.d.ts.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
5
5
  ],
6
6
  "name": "@lwc/style-compiler",
7
- "version": "8.24.0",
7
+ "version": "8.26.0",
8
8
  "description": "Transform style sheet to be consumed by the LWC engine",
9
9
  "keywords": [
10
10
  "lwc"
@@ -46,7 +46,7 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@lwc/shared": "8.24.0",
49
+ "@lwc/shared": "8.26.0",
50
50
  "postcss": "~8.5.6",
51
51
  "postcss-selector-parser": "~7.1.0",
52
52
  "postcss-value-parser": "~4.2.0"