@hypersolid-dev/eslint-config 5.0.0 → 6.0.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 (46) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +17 -1
  3. package/dist/base.js +14 -1
  4. package/dist/jest.d.ts +3 -0
  5. package/dist/jest.js +56 -0
  6. package/dist/plugins/props-destructuring.js +12 -12
  7. package/dist/plugins/react-native-a11y/rules/accessibility-actions-need-handler.js +2 -2
  8. package/dist/plugins/react-native-a11y/rules/accessibility-hint.js +2 -2
  9. package/dist/plugins/react-native-a11y/rules/accessibility-label.js +2 -2
  10. package/dist/plugins/react-native-a11y/rules/accessibility-role.js +2 -2
  11. package/dist/plugins/react-native-a11y/rules/accessibility-value-needs-range.js +25 -27
  12. package/dist/plugins/react-native-a11y/rules/adjustable-needs-value.js +16 -18
  13. package/dist/plugins/react-native-a11y/rules/gate-announce-for-accessibility.js +4 -4
  14. package/dist/plugins/react-native-a11y/rules/input-purpose.js +2 -2
  15. package/dist/plugins/react-native-a11y/rules/no-aria-legacy-mix.js +20 -22
  16. package/dist/plugins/react-native-a11y/rules/no-deprecated-accessibility-focus.js +12 -14
  17. package/dist/plugins/react-native-a11y/rules/no-dropped-aria-on-text.js +2 -2
  18. package/dist/plugins/react-native-a11y/rules/no-font-scaling-disabled.js +24 -26
  19. package/dist/plugins/react-native-a11y/rules/no-ineffective-accessibility-props.js +34 -36
  20. package/dist/plugins/react-native-a11y/rules/no-invalid-accessibility-state.js +31 -33
  21. package/dist/plugins/react-native-a11y/rules/no-nested-focus-stops.js +2 -2
  22. package/dist/plugins/react-native-a11y/rules/no-press-in-activation.js +2 -2
  23. package/dist/plugins/react-native-a11y/rules/no-unnamed-interactive.js +2 -2
  24. package/dist/plugins/react-native-a11y/rules/require-invert-colors-opt-out.js +2 -2
  25. package/dist/plugins/react-native-a11y/rules/require-modal-a11y-on-sheets.js +2 -2
  26. package/dist/plugins/react-native-a11y/utils/get-amount-of-text-children.js +15 -13
  27. package/dist/plugins/rules/boolean-guard.js +3 -3
  28. package/dist/plugins/rules/comment-style.js +43 -34
  29. package/dist/plugins/rules/effect-takes-callback.js +1 -1
  30. package/dist/plugins/rules/jsdoc-for-props.js +1 -1
  31. package/dist/plugins/rules/no-callback-payload.js +18 -15
  32. package/dist/plugins/rules/no-markup-variable.js +41 -35
  33. package/dist/plugins/rules/no-unnecessary-use-callback.js +2 -2
  34. package/dist/plugins/rules/regex-pattern-const.js +1 -1
  35. package/dist/plugins/rules/test-id-default.js +23 -27
  36. package/dist/plugins/rules/test-id-format.js +14 -18
  37. package/dist/plugins/rules/test-id-from-props.js +23 -27
  38. package/dist/plugins/rules/undefined-check.js +2 -2
  39. package/dist/plugins/rules/webview-allow-list.js +22 -26
  40. package/dist/plugins/utils/expressions.d.ts +3 -3
  41. package/dist/plugins/utils/expressions.js +94 -78
  42. package/dist/plugins/utils/types.js +14 -12
  43. package/dist/react-native.js +28 -0
  44. package/dist/shared.d.ts +3 -3
  45. package/dist/shared.js +168 -112
  46. package/package.json +12 -2
@@ -31,24 +31,20 @@ const PASCAL_CASE_PATTERN = /^[A-Z]/;
31
31
  const LOWER_TO_UPPER_PATTERN = /(?<=[a-z0-9])(?=[A-Z])/g;
32
32
  const ACRONYM_END_PATTERN = /(?<=[A-Z])(?=[A-Z][a-z])/g;
33
33
  /** `CompetitionLogo` -> `competition-logo`, `AdSlotURLBar` -> `ad-slot-url-bar`. */
34
- function kebab(name) {
35
- return name.replaceAll(LOWER_TO_UPPER_PATTERN, '-').replaceAll(ACRONYM_END_PATTERN, '-').toLowerCase();
36
- }
34
+ const kebab = (name) => name.replaceAll(LOWER_TO_UPPER_PATTERN, '-').replaceAll(ACRONYM_END_PATTERN, '-').toLowerCase();
37
35
  /** The component a file is named for: its basename, or its folder's for an `index` file. */
38
- function componentName(filename) {
36
+ const componentName = (filename) => {
39
37
  const name = path.basename(filename).replace(EXTENSION_PATTERN, '').replace(PLATFORM_PATTERN, '');
40
38
  return name === 'index' ? path.basename(path.dirname(filename)) : name;
41
- }
42
- function isHoc(callee) {
39
+ };
40
+ const isHoc = (callee) => {
43
41
  if (callee.type === AST_NODE_TYPES.Identifier)
44
42
  return HOC_NAMES.has(callee.name);
45
43
  return callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed && HOC_NAMES.has(callee.property.name);
46
- }
47
- function isFunction(node) {
48
- return Boolean(node && FUNCTION_TYPES.has(node.type));
49
- }
44
+ };
45
+ const isFunction = (node) => Boolean(node && FUNCTION_TYPES.has(node.type));
50
46
  /** Through `memo(…)`, `forwardRef(…)` and type assertions, to the component inside. */
51
- function unwrapComponent(node) {
47
+ const unwrapComponent = (node) => {
52
48
  let current = node ?? undefined;
53
49
  while (current) {
54
50
  if (TS_WRAPPERS.has(current.type))
@@ -59,31 +55,31 @@ function unwrapComponent(node) {
59
55
  return current;
60
56
  }
61
57
  return current;
62
- }
58
+ };
63
59
  /** What a function is held by, out through the calls and assertions it is declared behind. */
64
- function heldBy(fn) {
60
+ const heldBy = (fn) => {
65
61
  let held = fn.parent;
66
62
  while (held && (held.type === AST_NODE_TYPES.CallExpression || TS_WRAPPERS.has(held.type)))
67
63
  held = held.parent;
68
64
  return held;
69
- }
65
+ };
70
66
  /** Whether a function is the file's own component: named `component`, or an anonymous default export. */
71
- function isComponent(fn, component) {
67
+ const isComponent = (fn, component) => {
72
68
  if (fn.type === AST_NODE_TYPES.FunctionDeclaration)
73
69
  return !fn.id || fn.id.name === component;
74
70
  const held = heldBy(fn);
75
71
  if (held?.type === AST_NODE_TYPES.ExportDefaultDeclaration)
76
72
  return true;
77
73
  return held?.type === AST_NODE_TYPES.VariableDeclarator && held.id.type === AST_NODE_TYPES.Identifier && held.id.name === component;
78
- }
74
+ };
79
75
  /** The `testID` property of a destructured parameter or declarator. */
80
- function testIdProperty(pattern) {
76
+ const testIdProperty = (pattern) => {
81
77
  if (pattern?.type !== AST_NODE_TYPES.ObjectPattern)
82
78
  return undefined;
83
79
  return pattern.properties.find((property) => property.type === AST_NODE_TYPES.Property && !property.computed && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'testID');
84
- }
80
+ };
85
81
  /** The names a component's props object goes by: the first parameter, or the rest of a destructured one without `testID`. */
86
- function propsNames(fn) {
82
+ const propsNames = (fn) => {
87
83
  const first = fn.params.at(0);
88
84
  if (first?.type === AST_NODE_TYPES.Identifier)
89
85
  return [first.name];
@@ -91,15 +87,15 @@ function propsNames(fn) {
91
87
  return [];
92
88
  const rest = first.properties.find((property) => property.type === AST_NODE_TYPES.RestElement);
93
89
  return rest?.argument.type === AST_NODE_TYPES.Identifier ? [rest.argument.name] : [];
94
- }
90
+ };
95
91
  /** The file's own component that `node` sits in, however deep in callbacks. */
96
- function ownComponentAround(node, component) {
92
+ const ownComponentAround = (node, component) => {
97
93
  for (let current = node.parent; current; current = current.parent) {
98
94
  if (isFunction(current) && isComponent(current, component))
99
95
  return current;
100
96
  }
101
97
  return undefined;
102
- }
98
+ };
103
99
  const testIdDefault = createRule({
104
100
  name: 'test-id-default',
105
101
  meta: {
@@ -113,7 +109,7 @@ const testIdDefault = createRule({
113
109
  wrong: 'Default this to `"{{ expected }}"`, not `{{ actual }}`: the default is the component\'s own name in kebab-case, so a reader can predict the id without opening the file.',
114
110
  },
115
111
  },
116
- create(context) {
112
+ create: (context) => {
117
113
  const component = componentName(context.filename);
118
114
  // The file's own component only; anything else in here is private to it.
119
115
  if (!PASCAL_CASE_PATTERN.test(component))
@@ -146,7 +142,7 @@ const testIdDefault = createRule({
146
142
  check(testIdProperty(node.params[0]));
147
143
  };
148
144
  return {
149
- VariableDeclarator(node) {
145
+ VariableDeclarator: (node) => {
150
146
  // `const { testID } = props` inside the file's own component reads the same prop.
151
147
  if (node.id.type === AST_NODE_TYPES.ObjectPattern && node.init?.type === AST_NODE_TYPES.Identifier) {
152
148
  const fn = ownComponentAround(node, component);
@@ -157,20 +153,20 @@ const testIdDefault = createRule({
157
153
  if (node.id.type === AST_NODE_TYPES.Identifier)
158
154
  checkFunction(unwrapComponent(node.init), node.id.name);
159
155
  },
160
- FunctionDeclaration(node) {
156
+ FunctionDeclaration: (node) => {
161
157
  if (node.id)
162
158
  checkFunction(node, node.id.name);
163
159
  else if (node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration)
164
160
  checkFunction(node, component);
165
161
  },
166
162
  // `export default memo(({ testID }) => …)`: the anonymous component.
167
- ExportDefaultDeclaration(node) {
163
+ ExportDefaultDeclaration: (node) => {
168
164
  const init = unwrapComponent(node.declaration);
169
165
  if (init?.type !== AST_NODE_TYPES.FunctionDeclaration)
170
166
  checkFunction(init, component);
171
167
  },
172
168
  // `props.testID` read inside the component, with no default anywhere.
173
- MemberExpression(node) {
169
+ MemberExpression: (node) => {
174
170
  if (node.computed || node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== 'testID')
175
171
  return;
176
172
  if (node.object.type !== AST_NODE_TYPES.Identifier)
@@ -29,34 +29,30 @@ const LEADING_SEPARATOR_PATTERN = /^[^A-Za-z0-9]/;
29
29
  const TRAILING_SEPARATOR_PATTERN = /[^A-Za-z0-9]$/;
30
30
  const TEST_ID = 'testID';
31
31
  /** `Match.playerItem_photo` -> `match-player-item-photo`. */
32
- function kebab(value) {
33
- return value
34
- .replaceAll(LOWER_TO_UPPER_PATTERN, '-')
35
- .replaceAll(ACRONYM_END_PATTERN, '-')
36
- .replaceAll(NON_ALPHANUMERIC_RUN_PATTERN, '-')
37
- .replaceAll(EDGE_DASHES_PATTERN, '')
38
- .toLowerCase();
39
- }
32
+ const kebab = (value) => value
33
+ .replaceAll(LOWER_TO_UPPER_PATTERN, '-')
34
+ .replaceAll(ACRONYM_END_PATTERN, '-')
35
+ .replaceAll(NON_ALPHANUMERIC_RUN_PATTERN, '-')
36
+ .replaceAll(EDGE_DASHES_PATTERN, '')
37
+ .toLowerCase();
40
38
  /** A piece between substitutions keeps a separator where it meets one: `_photo` becomes `-photo`. */
41
- function kebabFragment(value) {
39
+ const kebabFragment = (value) => {
42
40
  const core = kebab(value);
43
41
  const lead = LEADING_SEPARATOR_PATTERN.test(value) ? '-' : '';
44
42
  const trail = TRAILING_SEPARATOR_PATTERN.test(value) ? '-' : '';
45
43
  return core ? `${lead}${core}${trail}` : '-';
46
- }
44
+ };
47
45
  /** Whether `inner` is written inside `outer`, rather than elsewhere in the file. */
48
- function isInside(outer, inner) {
49
- return inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
50
- }
46
+ const isInside = (outer, inner) => inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
51
47
  /** The written string an expression is, or `undefined` when it is not one. */
52
- function writtenString(node) {
48
+ const writtenString = (node) => {
53
49
  if (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string')
54
50
  return node.value;
55
51
  // A template with nothing substituted is a literal written the long way.
56
52
  if (node.type === AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0)
57
53
  return node.quasis[0]?.value.cooked ?? undefined;
58
54
  return undefined;
59
- }
55
+ };
60
56
  const testIdFormat = createRule({
61
57
  name: 'test-id-format',
62
58
  meta: {
@@ -68,7 +64,7 @@ const testIdFormat = createRule({
68
64
  format: 'Write this `testID` as `{{ suggestion }}`, not "{{ value }}": an id is lowercase kebab-case throughout, depth included. A dot is a wildcard in Maestro\'s `id:` matcher, and a capital is one shift key away from a flake — so a child derives with a dash, `` `${testID}-remote-image` ``, at every level.',
69
65
  },
70
66
  },
71
- create(context) {
67
+ create: (context) => {
72
68
  // Reported on the string when it sits inside the expression, else on the expression a name led from.
73
69
  const check = (expression, leaf) => {
74
70
  const value = writtenString(leaf);
@@ -101,7 +97,7 @@ const testIdFormat = createRule({
101
97
  }
102
98
  };
103
99
  return {
104
- JSXAttribute(node) {
100
+ JSXAttribute: (node) => {
105
101
  if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== TEST_ID)
106
102
  return;
107
103
  const { value } = node;
@@ -111,7 +107,7 @@ const testIdFormat = createRule({
111
107
  checkExpression(value.expression);
112
108
  },
113
109
  // A `testID` default, and a `testID:` in an options or fixture object.
114
- Property(node) {
110
+ Property: (node) => {
115
111
  if (node.computed || node.key.type !== AST_NODE_TYPES.Identifier || node.key.name !== TEST_ID)
116
112
  return;
117
113
  checkExpression(node.value.type === AST_NODE_TYPES.AssignmentPattern ? node.value.right : node.value);
@@ -23,11 +23,9 @@ import { leavesOf } from '../utils/expressions.js';
23
23
  */
24
24
  const TEST_ID = 'testID';
25
25
  /** A value spelled out where it stands, rather than reached for. */
26
- function isWritten(node) {
27
- return (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string') || (node.type === AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0);
28
- }
26
+ const isWritten = (node) => (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string') || (node.type === AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0);
29
27
  /** Whether an expression reaches outside itself for its value. */
30
- function readsAName(context, node) {
28
+ const isReadingAName = (context, node) => {
31
29
  switch (node.type) {
32
30
  // A local name bound to a written string is that string; anything unresolved is trusted.
33
31
  case AST_NODE_TYPES.Identifier:
@@ -41,26 +39,26 @@ function readsAName(context, node) {
41
39
  case AST_NODE_TYPES.TemplateLiteral:
42
40
  return node.expressions.length > 0 && node.quasis[0]?.value.cooked === '';
43
41
  case AST_NODE_TYPES.ConditionalExpression:
44
- return readsAName(context, node.consequent) || readsAName(context, node.alternate);
42
+ return isReadingAName(context, node.consequent) || isReadingAName(context, node.alternate);
45
43
  case AST_NODE_TYPES.LogicalExpression:
46
- return readsAName(context, node.left) || readsAName(context, node.right);
44
+ return isReadingAName(context, node.left) || isReadingAName(context, node.right);
47
45
  case AST_NODE_TYPES.TSAsExpression:
48
46
  case AST_NODE_TYPES.TSSatisfiesExpression:
49
47
  case AST_NODE_TYPES.TSNonNullExpression:
50
- return readsAName(context, node.expression);
48
+ return isReadingAName(context, node.expression);
51
49
  default:
52
50
  return false;
53
51
  }
54
- }
52
+ };
55
53
  /** How to name the offending id: a name is followed to the string behind it. */
56
- function writtenValue(context, node) {
54
+ const writtenValue = (context, node) => {
57
55
  if (node?.type === AST_NODE_TYPES.Literal && typeof node.value === 'string')
58
56
  return `"${node.value}"`;
59
57
  const strings = (node ? leavesOf(context, node) : [])
60
58
  .filter(isWritten)
61
59
  .map((leaf) => (leaf.type === AST_NODE_TYPES.Literal ? leaf.value : leaf.quasis[0]?.value.cooked));
62
60
  return strings.length === 1 ? `"${String(strings[0])}"` : 'a fixed value';
63
- }
61
+ };
64
62
  const testIdFromProps = createRule({
65
63
  name: 'test-id-from-props',
66
64
  meta: {
@@ -73,22 +71,20 @@ const testIdFromProps = createRule({
73
71
  prefixed: 'Start this `testID` from the `testID` prop rather than the written "{{ prefix }}": what follows it may differ per instance, but the prefix spells out a name the prop already carries, so a caller cannot scope or rename it. Write `` `${testID}-…` ``.',
74
72
  },
75
73
  },
76
- create(context) {
77
- return {
78
- JSXAttribute(node) {
79
- if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== TEST_ID)
80
- return;
81
- const { value } = node;
82
- const expression = value?.type === AST_NODE_TYPES.JSXExpressionContainer ? value.expression : (value ?? undefined);
83
- if (value?.type === AST_NODE_TYPES.JSXExpressionContainer && readsAName(context, value.expression))
84
- return;
85
- if (expression?.type === AST_NODE_TYPES.TemplateLiteral && expression.expressions.length > 0) {
86
- context.report({ node, messageId: 'prefixed', data: { prefix: expression.quasis[0]?.value.cooked } });
87
- return;
88
- }
89
- context.report({ node, messageId: 'static', data: { value: writtenValue(context, expression) } });
90
- },
91
- };
92
- },
74
+ create: (context) => ({
75
+ JSXAttribute: (node) => {
76
+ if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== TEST_ID)
77
+ return;
78
+ const { value } = node;
79
+ const expression = value?.type === AST_NODE_TYPES.JSXExpressionContainer ? value.expression : (value ?? undefined);
80
+ if (value?.type === AST_NODE_TYPES.JSXExpressionContainer && isReadingAName(context, value.expression))
81
+ return;
82
+ if (expression?.type === AST_NODE_TYPES.TemplateLiteral && expression.expressions.length > 0) {
83
+ context.report({ node, messageId: 'prefixed', data: { prefix: expression.quasis[0]?.value.cooked } });
84
+ return;
85
+ }
86
+ context.report({ node, messageId: 'static', data: { value: writtenValue(context, expression) } });
87
+ },
88
+ }),
93
89
  });
94
90
  export default testIdFromProps;
@@ -35,13 +35,13 @@ const undefinedCheck = createRule({
35
35
  redundant: "`{{ text }}` is `{{ suggestion }}` for this type: `undefined` is the only falsy value it admits, so the comparison draws no line. Write the value.",
36
36
  },
37
37
  },
38
- create(context) {
38
+ create: (context) => {
39
39
  const services = ESLintUtils.getParserServices(context, true);
40
40
  if (!services.program)
41
41
  return {};
42
42
  const checker = services.program.getTypeChecker();
43
43
  return {
44
- BinaryExpression(node) {
44
+ BinaryExpression: (node) => {
45
45
  if (!isCondition(node))
46
46
  return;
47
47
  const compared = undefinedComparison(node);
@@ -23,7 +23,7 @@ const SOURCE = 'react-native-webview';
23
23
  const WEBVIEW = 'WebView';
24
24
  const HANDLER = 'onShouldStartLoadWithRequest';
25
25
  /** Whether a JSX name resolves to the library's `WebView` import. */
26
- function isWebView(context, name) {
26
+ const isWebView = (context, name) => {
27
27
  const identifier = name.type === AST_NODE_TYPES.JSXMemberExpression ? name.object : name;
28
28
  if (identifier.type !== AST_NODE_TYPES.JSXIdentifier)
29
29
  return false;
@@ -44,9 +44,9 @@ function isWebView(context, name) {
44
44
  return false;
45
45
  const imported = node.imported.type === AST_NODE_TYPES.Identifier ? node.imported.name : node.imported.value;
46
46
  return imported === WEBVIEW;
47
- }
47
+ };
48
48
  /** `undefined`, `null`, or a branch that can be one: the prop is set and still absent. */
49
- function canBeAbsent(expression) {
49
+ const canBeAbsent = (expression) => {
50
50
  switch (expression.type) {
51
51
  case AST_NODE_TYPES.JSXEmptyExpression:
52
52
  return true;
@@ -61,18 +61,16 @@ function canBeAbsent(expression) {
61
61
  default:
62
62
  return false;
63
63
  }
64
- }
65
- function attribute(opening, name) {
66
- return opening.attributes.find((item) => item.type === AST_NODE_TYPES.JSXAttribute && item.name.name === name);
67
- }
64
+ };
65
+ const attribute = (opening, name) => opening.attributes.find((item) => item.type === AST_NODE_TYPES.JSXAttribute && item.name.name === name);
68
66
  /** Whether the prop's value is an inline array literal holding the string `"*"`. */
69
- function holdsWildcard(property) {
67
+ const isHoldingWildcard = (property) => {
70
68
  const expression = unwrap(property.value?.type === AST_NODE_TYPES.JSXExpressionContainer ? property.value.expression : undefined);
71
69
  if (expression?.type !== AST_NODE_TYPES.ArrayExpression)
72
70
  return false;
73
71
  return expression.elements.some((element) => (element?.type === AST_NODE_TYPES.Literal && element.value === '*') ||
74
72
  (element?.type === AST_NODE_TYPES.TemplateLiteral && element.expressions.length === 0 && element.quasis[0]?.value.cooked === '*'));
75
- }
73
+ };
76
74
  const webviewAllowList = createRule({
77
75
  name: 'webview-allow-list',
78
76
  meta: {
@@ -88,22 +86,20 @@ const webviewAllowList = createRule({
88
86
  narrowWhitelist: 'Make `originWhitelist` the inline `["*"]`: a URL outside the list skips `{{ handler }}` and the library opens it in another app, so the handler is the only allow-list that holds.',
89
87
  },
90
88
  },
91
- create(context) {
92
- return {
93
- JSXOpeningElement(node) {
94
- if (!isWebView(context, node.name))
95
- return;
96
- const handler = attribute(node, HANDLER);
97
- if (handler?.value?.type !== AST_NODE_TYPES.JSXExpressionContainer || canBeAbsent(handler.value.expression)) {
98
- context.report({ node, messageId: 'missingHandler', data: { handler: HANDLER } });
99
- }
100
- const whitelist = attribute(node, 'originWhitelist');
101
- if (!whitelist)
102
- context.report({ node, messageId: 'missingWhitelist', data: { handler: HANDLER } });
103
- else if (!holdsWildcard(whitelist))
104
- context.report({ node: whitelist, messageId: 'narrowWhitelist', data: { handler: HANDLER } });
105
- },
106
- };
107
- },
89
+ create: (context) => ({
90
+ JSXOpeningElement: (node) => {
91
+ if (!isWebView(context, node.name))
92
+ return;
93
+ const handler = attribute(node, HANDLER);
94
+ if (handler?.value?.type !== AST_NODE_TYPES.JSXExpressionContainer || canBeAbsent(handler.value.expression)) {
95
+ context.report({ node, messageId: 'missingHandler', data: { handler: HANDLER } });
96
+ }
97
+ const whitelist = attribute(node, 'originWhitelist');
98
+ if (!whitelist)
99
+ context.report({ node, messageId: 'missingWhitelist', data: { handler: HANDLER } });
100
+ else if (!isHoldingWildcard(whitelist))
101
+ context.report({ node: whitelist, messageId: 'narrowWhitelist', data: { handler: HANDLER } });
102
+ },
103
+ }),
108
104
  });
109
105
  export default webviewAllowList;
@@ -28,13 +28,13 @@ export type LeavesOptions = {
28
28
  seen?: Set<Node>;
29
29
  };
30
30
  /** Through `as`, `satisfies`, `!` and `?.`. */
31
- export declare function unwrap(node: Node | null | undefined): Node | undefined;
31
+ export declare const unwrap: (node: Node | null | undefined) => Node | undefined;
32
32
  /**
33
33
  * A property's key when it is written literally (`a`, `"a"`, `1`, `["a"]`,
34
34
  * `[\`a\`]`), else null: a computed key that is a variable, a call or a
35
35
  * template with expressions, a spread and a pattern have no key to read.
36
36
  */
37
- export declare function keyOf(property: Node): string | null;
37
+ export declare const keyOf: (property: Node) => string | null;
38
38
  /**
39
39
  * Every leaf an expression can produce.
40
40
  *
@@ -48,5 +48,5 @@ export declare function keyOf(property: Node): string | null;
48
48
  * itself: a name from another file, a `let`, a method call, a computed member,
49
49
  * a spread. A cycle yields nothing rather than looping.
50
50
  */
51
- export declare function leavesOf(context: ScopeContext, node: Node | null | undefined, options?: LeavesOptions): Node[];
51
+ export declare const leavesOf: (context: ScopeContext, node: Node | null | undefined, options?: LeavesOptions) => Node[];
52
52
  export {};