@drupal-canvas/eslint-config 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +6 -5
  2. package/dist/index.js +154 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -33,11 +33,12 @@ export default defineConfig([
33
33
  The following custom rules are part of the `required` config and validate Drupal
34
34
  Canvas Code Components:
35
35
 
36
- | Rule | Description |
37
- | ---------------------- | ------------------------------------------------------------------------------------------------------- |
38
- | `component-dir-name` | Validates that `machineName` matches the directory name (index-style) or filename prefix (named-style). |
39
- | `component-exports` | Validates that component has a default export. |
40
- | `component-prop-names` | Validates that component prop IDs match the camelCase version of their titles. |
36
+ | Rule | Description |
37
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------- |
38
+ | `component-dir-name` | Validates that `machineName` matches the directory name (index-style) or filename prefix (named-style). |
39
+ | `component-exports` | Validates that component has a default export. |
40
+ | `component-image-example-urls` | Validates that `canvas.module/image` prop examples use fully qualified image URLs. |
41
+ | `component-prop-names` | Validates that component prop IDs match the camelCase version of their titles. |
41
42
 
42
43
  ### Deprecated rules
43
44
 
package/dist/index.js CHANGED
@@ -320,9 +320,9 @@ var require_ignore = __commonJS({
320
320
  };
321
321
  }
322
322
  if (checkPattern(pattern.pattern)) {
323
- const rule8 = createRule(pattern, this._ignoreCase);
323
+ const rule9 = createRule(pattern, this._ignoreCase);
324
324
  this._added = true;
325
- this._rules.push(rule8);
325
+ this._rules.push(rule9);
326
326
  }
327
327
  }
328
328
  // @param {Array<string> | string | Ignore} pattern
@@ -344,18 +344,18 @@ var require_ignore = __commonJS({
344
344
  let ignored = false;
345
345
  let unignored = false;
346
346
  let matchedRule;
347
- this._rules.forEach((rule8) => {
348
- const { negative } = rule8;
347
+ this._rules.forEach((rule9) => {
348
+ const { negative } = rule9;
349
349
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
350
350
  return;
351
351
  }
352
- const matched = rule8[mode].test(path2);
352
+ const matched = rule9[mode].test(path2);
353
353
  if (!matched) {
354
354
  return;
355
355
  }
356
356
  ignored = !negative;
357
357
  unignored = negative;
358
- matchedRule = negative ? UNDEFINED : rule8;
358
+ matchedRule = negative ? UNDEFINED : rule9;
359
359
  });
360
360
  const ret = {
361
361
  ignored,
@@ -668,6 +668,104 @@ var rule2 = {
668
668
  }
669
669
  };
670
670
  var component_exports_default = rule2;
671
+
672
+ // src/rules/component-image-example-urls.ts
673
+ var IMAGE_REF = "json-schema-definitions://canvas.module/image";
674
+ function getMappingPair(mapping, key) {
675
+ return mapping.pairs.find((pair) => getYAMLStringValue(pair.key) === key);
676
+ }
677
+ function isYamlMapping(node) {
678
+ return node?.type === "YAMLMapping";
679
+ }
680
+ function isYamlSequence(node) {
681
+ return node?.type === "YAMLSequence";
682
+ }
683
+ function hasImageRef(propMapping) {
684
+ const ref = getYAMLStringValue(
685
+ getMappingPair(propMapping, "$ref")?.value ?? null
686
+ );
687
+ if (ref === IMAGE_REF) {
688
+ return true;
689
+ }
690
+ const itemsValue = getMappingPair(propMapping, "items")?.value;
691
+ if (!isYamlMapping(itemsValue)) {
692
+ return false;
693
+ }
694
+ return getYAMLStringValue(getMappingPair(itemsValue, "$ref")?.value ?? null) === IMAGE_REF;
695
+ }
696
+ function isFullyQualifiedUrl(value) {
697
+ try {
698
+ const parsed = new URL(value);
699
+ return parsed.protocol.length > 0 && parsed.hostname.length > 0;
700
+ } catch {
701
+ return false;
702
+ }
703
+ }
704
+ function getInvalidImageExampleNodes(examplesValue) {
705
+ if (!isYamlSequence(examplesValue)) {
706
+ return [];
707
+ }
708
+ const invalidNodes = [];
709
+ for (const example of examplesValue.entries) {
710
+ const imageEntries = isYamlSequence(example) ? example.entries : [example];
711
+ for (const imageEntry of imageEntries) {
712
+ if (!isYamlMapping(imageEntry)) {
713
+ continue;
714
+ }
715
+ const srcNode = getMappingPair(imageEntry, "src")?.value;
716
+ if (srcNode?.type === "YAMLScalar" && typeof srcNode.value === "string" && !isFullyQualifiedUrl(srcNode.value)) {
717
+ invalidNodes.push(srcNode);
718
+ }
719
+ }
720
+ }
721
+ return invalidNodes;
722
+ }
723
+ var rule3 = {
724
+ meta: {
725
+ type: "problem",
726
+ docs: {
727
+ description: "Validates that default examples for image props use fully-qualified URLs"
728
+ }
729
+ },
730
+ create(context) {
731
+ if (!isComponentYmlFile(context.filename)) {
732
+ return {};
733
+ }
734
+ return {
735
+ YAMLPair(node) {
736
+ const keyName = getYAMLStringValue(node.key);
737
+ if (keyName !== "props" || !isYamlMapping(node.value)) {
738
+ return;
739
+ }
740
+ const propertiesValue = getMappingPair(node.value, "properties")?.value;
741
+ if (!isYamlMapping(propertiesValue)) {
742
+ return;
743
+ }
744
+ for (const propPair of propertiesValue.pairs) {
745
+ const propId = getYAMLStringValue(propPair.key);
746
+ if (!propId || !isYamlMapping(propPair.value)) {
747
+ continue;
748
+ }
749
+ if (!hasImageRef(propPair.value)) {
750
+ continue;
751
+ }
752
+ const examplesValue = getMappingPair(
753
+ propPair.value,
754
+ "examples"
755
+ )?.value;
756
+ const invalidNodes = getInvalidImageExampleNodes(examplesValue);
757
+ for (const invalidNode of invalidNodes) {
758
+ context.report({
759
+ node: invalidNode,
760
+ message: `Image prop "${propId}" example src must be a fully-qualified URL with both scheme and host. Use a placeholder URL such as https://placehold.co/600x400.`
761
+ });
762
+ }
763
+ }
764
+ }
765
+ };
766
+ }
767
+ };
768
+ var component_image_example_urls_default = rule3;
671
769
  var Gt = (n7, t, e) => {
672
770
  let s = n7 instanceof RegExp ? ce(n7, e) : n7, i = t instanceof RegExp ? ce(t, e) : t, r = s !== null && i != null && ss(s, i, e);
673
771
  return r && { start: r[0], end: r[1], pre: e.slice(0, r[0]), body: e.slice(r[0] + s.length, r[1]), post: e.slice(r[1] + i.length) };
@@ -4447,7 +4545,7 @@ function checkImportSource(context, node, source) {
4447
4545
  if (source.startsWith("@fontsource")) {
4448
4546
  context.report({
4449
4547
  node,
4450
- message: `Importing font packages ("${source}") is not supported in components. Configure fonts in the Brand kit instead.`
4548
+ message: `Importing font packages ("${source}") is not supported in components.`
4451
4549
  });
4452
4550
  return;
4453
4551
  }
@@ -4508,7 +4606,7 @@ function checkImportSource(context, node, source) {
4508
4606
  if (source === "@/lib/FormattedText") {
4509
4607
  context.report({
4510
4608
  node,
4511
- message: "The `FormattedText` component was moved into the `drupal-canvas` package.",
4609
+ message: "The `FormattedText` component was moved into the `drupal-canvas` package. The `@/lib/FormattedText` path is provided by Canvas and cannot be used for local files.",
4512
4610
  fix(fixer) {
4513
4611
  if (node.type === "ImportDeclaration" && node.specifiers.length === 1 && node.specifiers[0].local.name === "FormattedText") {
4514
4612
  return fixer.replaceText(
@@ -4521,6 +4619,42 @@ function checkImportSource(context, node, source) {
4521
4619
  });
4522
4620
  return;
4523
4621
  }
4622
+ if (source === "@/lib/utils") {
4623
+ context.report({
4624
+ node,
4625
+ message: "Utilities were moved into the `drupal-canvas` package. The `@/lib/utils` path is provided by Canvas and cannot be used for local files.",
4626
+ fix(fixer) {
4627
+ return fixer.replaceText(node.source, "'drupal-canvas'");
4628
+ }
4629
+ });
4630
+ return;
4631
+ }
4632
+ if (source === "@/lib/jsonapi-utils") {
4633
+ context.report({
4634
+ node,
4635
+ message: "JSON:API utilities were moved into the `drupal-canvas` package. The `@/lib/jsonapi-utils` path is provided by Canvas and cannot be used for local files.",
4636
+ fix(fixer) {
4637
+ return fixer.replaceText(node.source, "'drupal-canvas'");
4638
+ }
4639
+ });
4640
+ return;
4641
+ }
4642
+ if (source === "@/lib/drupal-utils") {
4643
+ context.report({
4644
+ node,
4645
+ message: "Drupal utilities were moved into the `drupal-canvas` package. The `@/lib/drupal-utils` path is provided by Canvas and cannot be used for local files.",
4646
+ fix(fixer) {
4647
+ const importsSortMenu = node.type === "ImportDeclaration" && node.specifiers.some(
4648
+ (specifier) => specifier.local.name === "sortMenu" || specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "sortMenu"
4649
+ );
4650
+ if (!importsSortMenu) {
4651
+ return fixer.replaceText(node.source, "'drupal-canvas'");
4652
+ }
4653
+ return null;
4654
+ }
4655
+ });
4656
+ return;
4657
+ }
4524
4658
  if (source.startsWith("@/")) {
4525
4659
  const suffix = source.slice(2);
4526
4660
  const config = resolveCanvasConfig({ hostRoot: context.cwd });
@@ -4536,7 +4670,7 @@ function checkImportSource(context, node, source) {
4536
4670
  return;
4537
4671
  }
4538
4672
  }
4539
- var rule3 = {
4673
+ var rule4 = {
4540
4674
  meta: {
4541
4675
  type: "problem",
4542
4676
  docs: {
@@ -4562,7 +4696,7 @@ var rule3 = {
4562
4696
  };
4563
4697
  }
4564
4698
  };
4565
- var component_imports_default = rule3;
4699
+ var component_imports_default = rule4;
4566
4700
  function extractProps(propsNode) {
4567
4701
  if (!propsNode.value || propsNode.value.type !== "YAMLMapping") {
4568
4702
  return [];
@@ -4596,7 +4730,7 @@ function extractProps(propsNode) {
4596
4730
  }
4597
4731
  return props;
4598
4732
  }
4599
- var rule4 = {
4733
+ var rule5 = {
4600
4734
  meta: {
4601
4735
  type: "problem",
4602
4736
  docs: {
@@ -4637,7 +4771,7 @@ var rule4 = {
4637
4771
  };
4638
4772
  }
4639
4773
  };
4640
- var component_prop_names_default = rule4;
4774
+ var component_prop_names_default = rule5;
4641
4775
 
4642
4776
  // src/configs/required.ts
4643
4777
  var required = defineConfig([
@@ -4663,6 +4797,7 @@ var required = defineConfig([
4663
4797
  rules: {
4664
4798
  "component-dir-name": component_dir_name_default,
4665
4799
  "component-exports": component_exports_default,
4800
+ "component-image-example-urls": component_image_example_urls_default,
4666
4801
  "component-imports": component_imports_default,
4667
4802
  "component-prop-names": component_prop_names_default
4668
4803
  }
@@ -4671,6 +4806,7 @@ var required = defineConfig([
4671
4806
  rules: {
4672
4807
  "drupal-canvas/component-dir-name": "error",
4673
4808
  "drupal-canvas/component-exports": "error",
4809
+ "drupal-canvas/component-image-example-urls": "error",
4674
4810
  "drupal-canvas/component-imports": "error",
4675
4811
  "drupal-canvas/component-prop-names": "error"
4676
4812
  }
@@ -4718,7 +4854,7 @@ var IGNORED_FILES = [
4718
4854
  function isFileAllowed(fileName, allowedFiles) {
4719
4855
  return allowedFiles.some((allowedFile) => allowedFile === fileName);
4720
4856
  }
4721
- var rule5 = {
4857
+ var rule6 = {
4722
4858
  meta: {
4723
4859
  type: "problem",
4724
4860
  docs: {
@@ -4757,7 +4893,7 @@ var rule5 = {
4757
4893
  };
4758
4894
  }
4759
4895
  };
4760
- var component_files_default = rule5;
4896
+ var component_files_default = rule6;
4761
4897
  function checkImportSource2(context, node, source) {
4762
4898
  if (source.startsWith("./") || source.startsWith("../")) {
4763
4899
  context.report({
@@ -4886,7 +5022,7 @@ function checkImportSource2(context, node, source) {
4886
5022
  message: `Importing "${source}" is not supported. If this is a local import via a path alias, use the "@/components/" alias instead. If you are importing a third-party package, see the list of supported packages at https://project.pages.drupalcode.org/canvas/code-components/packages. (The status of supporting any third-party package can be tracked at https://drupal.org/i/3560197.)`
4887
5023
  });
4888
5024
  }
4889
- var rule6 = {
5025
+ var rule7 = {
4890
5026
  meta: {
4891
5027
  type: "problem",
4892
5028
  docs: {
@@ -4913,7 +5049,7 @@ var rule6 = {
4913
5049
  };
4914
5050
  }
4915
5051
  };
4916
- var component_imports_deprecated_default = rule6;
5052
+ var component_imports_deprecated_default = rule7;
4917
5053
  function findTopmostComponentsParentDir(currentParentDir, rootDir) {
4918
5054
  if (currentParentDir === rootDir) {
4919
5055
  return currentParentDir;
@@ -4934,7 +5070,7 @@ function hasComponentSubdirectories(dirPath) {
4934
5070
  }
4935
5071
  return false;
4936
5072
  }
4937
- var rule7 = {
5073
+ var rule8 = {
4938
5074
  meta: {
4939
5075
  type: "problem",
4940
5076
  docs: {
@@ -4969,7 +5105,7 @@ var rule7 = {
4969
5105
  };
4970
5106
  }
4971
5107
  };
4972
- var component_no_hierarchy_default = rule7;
5108
+ var component_no_hierarchy_default = rule8;
4973
5109
 
4974
5110
  // src/configs/requiredDeprecated.ts
4975
5111
  var required2 = defineConfig([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drupal-canvas/eslint-config",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "ESLint config for validating Drupal Canvas Code Components",
5
5
  "license": "MIT",
6
6
  "repository": {