@lipemat/eslint-config 5.0.0-beta.2 → 5.0.0-beta.4

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.
package/index.js CHANGED
@@ -7,10 +7,6 @@ import globals from 'globals';
7
7
  import stylisticTs from '@stylistic/eslint-plugin-ts';
8
8
  import { getConfig } from './helpers/config.js';
9
9
  const flatCompat = new FlatCompat();
10
- /**
11
- * Default config if no extensions override it.
12
- *
13
- */
14
10
  const BASE_CONFIG = {
15
11
  languageOptions: {
16
12
  ecmaVersion: 7,
@@ -115,16 +111,20 @@ const TS_CONFIG = {
115
111
  /**
116
112
  * Merge in any extensions' config.
117
113
  */
118
- let mergedConfig = [BASE_CONFIG, TS_CONFIG];
114
+ const defaultConfig = [
115
+ BASE_CONFIG,
116
+ TS_CONFIG,
117
+ securityPlugin.configs.recommended,
118
+ ];
119
+ let mergedConfig = [];
119
120
  try {
120
- mergedConfig = getConfig(mergedConfig);
121
+ mergedConfig = getConfig(defaultConfig);
121
122
  }
122
123
  catch (e) {
124
+ // JS Boilerplate is likely not installed.
123
125
  console.debug(e);
124
- // JS Boilerplate is not installed.
125
126
  }
126
127
  export default [
127
- ...securityPlugin.configs.recommended,
128
128
  ...fixupConfigRules(flatCompat.extends('plugin:@wordpress/eslint-plugin/recommended-with-formatting')),
129
129
  ...fixupConfigRules(flatCompat.extends('plugin:deprecation/recommended')),
130
130
  ...mergedConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lipemat/eslint-config",
3
- "version": "5.0.0-beta.2",
3
+ "version": "5.0.0-beta.4",
4
4
  "license": "MIT",
5
5
  "description": "Eslint configuration for all @lipemat packages",
6
6
  "engines": {
@@ -25,27 +25,25 @@ const plugin = {
25
25
  'window-escaping': windowEscaping,
26
26
  },
27
27
  configs: {
28
- recommended: [],
28
+ recommended: {},
29
29
  },
30
30
  };
31
31
  // Freeze the plugin to prevent modifications and use the plugin within.
32
32
  plugin.configs = Object.freeze({
33
- recommended: [
34
- {
35
- plugins: {
36
- '@lipemat/security': plugin,
37
- },
38
- rules: {
39
- '@lipemat/security/dangerously-set-inner-html': 'error',
40
- '@lipemat/security/html-executing-assignment': 'error',
41
- '@lipemat/security/html-executing-function': 'error',
42
- '@lipemat/security/html-sinks': 'error',
43
- '@lipemat/security/html-string-concat': 'error',
44
- '@lipemat/security/jquery-executing': 'error',
45
- '@lipemat/security/vulnerable-tag-stripping': 'error',
46
- '@lipemat/security/window-escaping': 'error',
47
- },
33
+ recommended: {
34
+ plugins: {
35
+ '@lipemat/security': plugin,
48
36
  },
49
- ],
37
+ rules: {
38
+ '@lipemat/security/dangerously-set-inner-html': 'error',
39
+ '@lipemat/security/html-executing-assignment': 'error',
40
+ '@lipemat/security/html-executing-function': 'error',
41
+ '@lipemat/security/html-sinks': 'error',
42
+ '@lipemat/security/html-string-concat': 'error',
43
+ '@lipemat/security/jquery-executing': 'error',
44
+ '@lipemat/security/vulnerable-tag-stripping': 'error',
45
+ '@lipemat/security/window-escaping': 'error',
46
+ },
47
+ },
50
48
  });
51
49
  export default plugin;
@@ -29,12 +29,15 @@ const plugin = {
29
29
  defaultOptions: [],
30
30
  meta: {
31
31
  type: 'problem',
32
- fixable: 'code',
32
+ hasSuggestions: true,
33
33
  docs: {
34
34
  description: 'Disallow using unsanitized values in dangerouslySetInnerHTML',
35
35
  },
36
36
  messages: {
37
37
  dangerousInnerHtml: 'Any HTML passed to `dangerouslySetInnerHTML` gets executed. Please make sure it\'s properly escaped.',
38
+ // Suggestions
39
+ domPurify: 'Wrap the content with a `DOMPurify.sanitize()` call.',
40
+ sanitize: 'Wrap the content with a `sanitize()` call.',
38
41
  },
39
42
  schema: [],
40
43
  },
@@ -51,9 +54,20 @@ const plugin = {
51
54
  context.report({
52
55
  node,
53
56
  messageId: 'dangerousInnerHtml',
54
- fix: (fixer) => {
55
- return fixer.replaceText(node, `dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(${context.sourceCode.getText(htmlValue)})}}`);
56
- },
57
+ suggest: [
58
+ {
59
+ messageId: 'domPurify',
60
+ fix: (fixer) => {
61
+ return fixer.replaceText(node, `dangerouslySetInnerHTML={{__html: DOMPurify.sanitize( ${context.sourceCode.getText(htmlValue)} )}}`);
62
+ },
63
+ },
64
+ {
65
+ messageId: 'sanitize',
66
+ fix: (fixer) => {
67
+ return fixer.replaceText(node, `dangerouslySetInnerHTML={{__html: sanitize( ${context.sourceCode.getText(htmlValue)} )}}`);
68
+ },
69
+ },
70
+ ],
57
71
  });
58
72
  },
59
73
  };
@@ -1,7 +1,8 @@
1
1
  import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
- import { isSanitized } from '../utils/shared.js';
2
+ import { isDomElementType, isSafeLiteralString, isSanitized } from '../utils/shared.js';
3
3
  const UNSAFE_PROPERTIES = [
4
- 'innerHTML', 'outerHTML',
4
+ 'innerHTML',
5
+ 'outerHTML',
5
6
  ];
6
7
  function isUnsafeProperty(propertyName) {
7
8
  return UNSAFE_PROPERTIES.includes(propertyName);
@@ -12,7 +13,6 @@ const plugin = {
12
13
  docs: {
13
14
  description: 'Disallow using unsanitized values in HTML executing property assignments',
14
15
  },
15
- fixable: 'code',
16
16
  hasSuggestions: true,
17
17
  messages: {
18
18
  executed: 'Any HTML used with `{{propertyName}}` gets executed. Make sure it\'s properly escaped.',
@@ -26,8 +26,7 @@ const plugin = {
26
26
  create(context) {
27
27
  return {
28
28
  AssignmentExpression(node) {
29
- // Handle element.innerHTML = value and element.outerHTML = value
30
- if (AST_NODE_TYPES.MemberExpression !== node.left.type || !('name' in node.left.property)) {
29
+ if (AST_NODE_TYPES.MemberExpression !== node.left.type || AST_NODE_TYPES.Identifier !== node.left.property.type) {
31
30
  return;
32
31
  }
33
32
  const propertyName = node.left.property.name;
@@ -35,7 +34,7 @@ const plugin = {
35
34
  return;
36
35
  }
37
36
  const value = node.right;
38
- if (!isSanitized(value)) {
37
+ if (!isSafeLiteralString(value) && !isSanitized(value) && isDomElementType(node.left.object, context)) {
39
38
  context.report({
40
39
  node,
41
40
  messageId: 'executed',
@@ -47,14 +46,14 @@ const plugin = {
47
46
  messageId: 'domPurify',
48
47
  fix: (fixer) => {
49
48
  const valueText = context.sourceCode.getText(value);
50
- return fixer.replaceText(value, `DOMPurify.sanitize(${valueText})`);
49
+ return fixer.replaceText(value, `DOMPurify.sanitize( ${valueText} )`);
51
50
  },
52
51
  },
53
52
  {
54
53
  messageId: 'sanitize',
55
54
  fix: (fixer) => {
56
55
  const valueText = context.sourceCode.getText(value);
57
- return fixer.replaceText(value, `sanitize(${valueText})`);
56
+ return fixer.replaceText(value, `sanitize( ${valueText} )`);
58
57
  },
59
58
  },
60
59
  ],
@@ -1,5 +1,5 @@
1
1
  import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
- import { isDomElementType, isSanitized } from '../utils/shared.js';
2
+ import { isDomElementType, isSafeLiteralString, isSanitized } from '../utils/shared.js';
3
3
  import { isJQueryCall } from './jquery-executing.js';
4
4
  const DOCUMENT_METHODS = [
5
5
  'document.write',
@@ -42,19 +42,20 @@ function getDocumentCall(node) {
42
42
  }
43
43
  return null;
44
44
  }
45
- function getElementMethodCall(node) {
46
- // Detect element.method(userInput) calls
47
- if (AST_NODE_TYPES.MemberExpression !== node.callee.type || !('name' in node.callee.property)) {
45
+ function getElementMethodCall(node, context) {
46
+ if (AST_NODE_TYPES.MemberExpression !== node.callee.type || AST_NODE_TYPES.Identifier !== node.callee.property.type) {
48
47
  return null;
49
48
  }
50
49
  const methodName = node.callee.property.name;
50
+ if (!isDomElementType(node.callee.object, context)) {
51
+ return null; // We only care about DOM element method calls.
52
+ }
51
53
  if (!isUnsafeMethod(methodName)) {
52
54
  return null;
53
55
  }
54
56
  if (isJQueryCall(node)) {
55
57
  return null; // Handled in jquery-executing rule
56
58
  }
57
- // This is a generic element method call, not jQuery specific
58
59
  return methodName;
59
60
  }
60
61
  const plugin = {
@@ -63,7 +64,6 @@ const plugin = {
63
64
  docs: {
64
65
  description: 'Disallow using unsanitized values in functions that execute HTML',
65
66
  },
66
- fixable: 'code',
67
67
  hasSuggestions: true,
68
68
  messages: {
69
69
  'document.write': 'Any HTML used with `document.write` gets executed. Make sure it\'s properly escaped.',
@@ -91,7 +91,7 @@ const plugin = {
91
91
  method = documentMethod;
92
92
  }
93
93
  else {
94
- method = getElementMethodCall(node);
94
+ method = getElementMethodCall(node, context);
95
95
  if (null === method) {
96
96
  return;
97
97
  }
@@ -100,7 +100,7 @@ const plugin = {
100
100
  if (isSecondArgMethod(method)) {
101
101
  arg = node.arguments[1];
102
102
  }
103
- if (!isSanitized(arg) && !isDomElementType(arg, context)) {
103
+ if (!isSafeLiteralString(arg) && !isSanitized(arg) && !isDomElementType(arg, context)) {
104
104
  context.report({
105
105
  node,
106
106
  messageId: method,
@@ -109,14 +109,14 @@ const plugin = {
109
109
  messageId: 'domPurify',
110
110
  fix: (fixer) => {
111
111
  const argText = context.sourceCode.getText(arg);
112
- return fixer.replaceText(arg, `DOMPurify.sanitize(${argText})`);
112
+ return fixer.replaceText(arg, `DOMPurify.sanitize( ${argText} )`);
113
113
  },
114
114
  },
115
115
  {
116
116
  messageId: 'sanitize',
117
117
  fix: (fixer) => {
118
118
  const argText = context.sourceCode.getText(arg);
119
- return fixer.replaceText(arg, `sanitize(${argText})`);
119
+ return fixer.replaceText(arg, `sanitize( ${argText} )`);
120
120
  },
121
121
  },
122
122
  ],
@@ -1,11 +1,5 @@
1
1
  import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
- import { isSanitized, isStringLike } from '../utils/shared.js';
3
- /**
4
- * Check if a node is a literal string
5
- */
6
- function isLiteralString(node) {
7
- return AST_NODE_TYPES.Literal === node.type && 'string' === typeof node.value;
8
- }
2
+ import { isLiteralString, isSanitized, isStringLike } from '../utils/shared.js';
9
3
  /**
10
4
  * Get the callee name from a call expression
11
5
  */
@@ -87,11 +81,11 @@ const plugin = {
87
81
  suggest: [
88
82
  {
89
83
  messageId: 'domPurify',
90
- fix: fixer => fixer.replaceText(firstArg, `DOMPurify.sanitize(${argText})`),
84
+ fix: fixer => fixer.replaceText(firstArg, `DOMPurify.sanitize( ${argText} )`),
91
85
  },
92
86
  {
93
87
  messageId: 'sanitize',
94
- fix: fixer => fixer.replaceText(firstArg, `sanitize(${argText})`),
88
+ fix: fixer => fixer.replaceText(firstArg, `sanitize( ${argText} )`),
95
89
  },
96
90
  ],
97
91
  });
@@ -111,11 +105,11 @@ const plugin = {
111
105
  suggest: [
112
106
  {
113
107
  messageId: 'domPurify',
114
- fix: fixer => fixer.replaceText(node.right, `DOMPurify.sanitize(${rightText})`),
108
+ fix: fixer => fixer.replaceText(node.right, `DOMPurify.sanitize( ${rightText} )`),
115
109
  },
116
110
  {
117
111
  messageId: 'sanitize',
118
- fix: fixer => fixer.replaceText(node.right, `sanitize(${rightText})`),
112
+ fix: fixer => fixer.replaceText(node.right, `sanitize( ${rightText} )`),
119
113
  },
120
114
  ],
121
115
  });
@@ -3,7 +3,12 @@ function isStringConcat(node) {
3
3
  // 'foo' + userInput + 'bar' (HTML-like only)
4
4
  return AST_NODE_TYPES.BinaryExpression === node.type && '+' === node.operator && hasHtmlLikeStrings(node);
5
5
  }
6
- function hasHtmlLikeStrings(node) {
6
+ /**
7
+ * Check if an expression contains any HTML-like strings.
8
+ * - Looks for `<` or `>` characters in string literals and template literals.
9
+ * - Recursively checks binary expressions with the ` + ` operator.
10
+ */
11
+ export function hasHtmlLikeStrings(node) {
7
12
  if (AST_NODE_TYPES.Literal === node.type && 'string' === typeof node.value) {
8
13
  return /[<>]/.test(node.value);
9
14
  }
@@ -55,7 +55,6 @@ const plugin = {
55
55
  docs: {
56
56
  description: 'Disallow using unsanitized values in jQuery methods that execute HTML',
57
57
  },
58
- fixable: 'code',
59
58
  hasSuggestions: true,
60
59
  messages: {
61
60
  needsEscaping: 'Any HTML used with `{{methodName}}` gets executed. Make sure it\'s properly escaped.',
@@ -84,14 +83,14 @@ const plugin = {
84
83
  messageId: 'domPurify',
85
84
  fix: (fixer) => {
86
85
  const argText = context.sourceCode.getText(arg);
87
- return fixer.replaceText(arg, `DOMPurify.sanitize(${argText})`);
86
+ return fixer.replaceText(arg, `DOMPurify.sanitize( ${argText} )`);
88
87
  },
89
88
  },
90
89
  {
91
90
  messageId: 'sanitize',
92
91
  fix: (fixer) => {
93
92
  const argText = context.sourceCode.getText(arg);
94
- return fixer.replaceText(arg, `sanitize(${argText})`);
93
+ return fixer.replaceText(arg, `sanitize( ${argText} )`);
95
94
  },
96
95
  },
97
96
  ],
@@ -33,7 +33,6 @@ const plugin = {
33
33
  docs: {
34
34
  description: 'Disallow jQuery .html().text() chaining which can lead to XSS through tag stripping',
35
35
  },
36
- fixable: 'code',
37
36
  hasSuggestions: true,
38
37
  messages: {
39
38
  vulnerableTagStripping: 'Using .html().text() can lead to XSS vulnerabilities through tag stripping. Use only .text()',
@@ -1,5 +1,5 @@
1
1
  import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
- import { isSanitized } from '../utils/shared.js';
2
+ import { isLiteralString, isSanitized } from '../utils/shared.js';
3
3
  // Window and location properties that need special handling
4
4
  const LOCATION_PROPS = new Set(['href', 'src', 'action',
5
5
  'protocol', 'host', 'hostname', 'pathname', 'search', 'hash', 'username', 'port', 'name', 'status',
@@ -9,37 +9,7 @@ export function isSafeUrlString(value) {
9
9
  return !/^\s*(?:javascript|data|vbscript|about|livescript)\s*:/i.test(decodeURIComponent(value.replace(/[\u0000-\u001F\u007F]+/g, '')));
10
10
  }
11
11
  function isSafeUrlLiteral(node) {
12
- if (AST_NODE_TYPES.TemplateElement !== node.type && AST_NODE_TYPES.Literal !== node.type) {
13
- return false;
14
- }
15
- if (typeof node.value !== 'string') {
16
- return false;
17
- }
18
- return isSafeUrlString(node.value);
19
- }
20
- function isSafeUrlTemplate(node) {
21
- if (AST_NODE_TYPES.TemplateLiteral !== node.type || 0 === node.quasis.length) {
22
- return false;
23
- }
24
- // Basic scheme safety on the first static chunk
25
- const firstChunk = node.quasis[0];
26
- if (isSafeUrlLiteral(firstChunk)) {
27
- return true;
28
- }
29
- return isUrlEncoded(node);
30
- }
31
- function isUrlEncoded(node) {
32
- if (AST_NODE_TYPES.TemplateLiteral !== node.type) {
33
- return false;
34
- }
35
- return Array.isArray(node.expressions) && node.expressions.length > 0 && node.expressions.every(isEncoded);
36
- }
37
- function isEncoded(node) {
38
- if (AST_NODE_TYPES.CallExpression !== node.type) {
39
- return false;
40
- }
41
- return AST_NODE_TYPES.Identifier === node.callee.type &&
42
- ('encodeURIComponent' === node.callee.name || 'encodeURI' === node.callee.name);
12
+ return isLiteralString(node) && isSafeUrlString(node.value);
43
13
  }
44
14
  function isWindowLocationAssignment(node) {
45
15
  // window.location.<prop> = ...
@@ -60,16 +30,16 @@ function isWindowAssignment(node) {
60
30
  'window' === node.left.object.name &&
61
31
  WINDOW_PROPS.has(node.left.property.name));
62
32
  }
63
- function isWindowOrLocationMemberExpression(memberExpr) {
33
+ function isWindowOrLocation(expression) {
64
34
  // Helper to detect a window.* or window.location.*
65
- if (AST_NODE_TYPES.MemberExpression !== memberExpr.type) {
35
+ if (AST_NODE_TYPES.MemberExpression !== expression.type) {
66
36
  return false;
67
37
  }
68
- if (AST_NODE_TYPES.Identifier === memberExpr.object.type && 'window' === memberExpr.object.name) {
38
+ if (AST_NODE_TYPES.Identifier === expression.object.type && 'window' === expression.object.name) {
69
39
  return true;
70
40
  }
71
- if (AST_NODE_TYPES.MemberExpression === memberExpr.object.type) {
72
- const memberObject = memberExpr.object;
41
+ if (AST_NODE_TYPES.MemberExpression === expression.object.type) {
42
+ const memberObject = expression.object;
73
43
  const isObjectWindow = AST_NODE_TYPES.Identifier === memberObject.object.type && 'window' === memberObject.object.name;
74
44
  const isPropertyLocation = AST_NODE_TYPES.Identifier === memberObject.property.type && 'location' === memberObject.property.name;
75
45
  return isObjectWindow && isPropertyLocation;
@@ -108,10 +78,7 @@ const plugin = {
108
78
  if (!LOCATION_PROPS.has(propName)) {
109
79
  return;
110
80
  }
111
- if (isSafeUrlLiteral(rhsResolved) || isSafeUrlTemplate(rhsResolved)) {
112
- return;
113
- }
114
- if (isSanitized(rhsResolved)) {
81
+ if (isSafeUrlLiteral(rhsResolved) || isSanitized(rhsResolved)) {
115
82
  return;
116
83
  }
117
84
  context.report({
@@ -125,13 +92,13 @@ const plugin = {
125
92
  messageId: 'sanitize',
126
93
  fix: (fixer) => {
127
94
  const argText = context.sourceCode.getText(right);
128
- return fixer.replaceText(right, `sanitize(${argText})`);
95
+ return fixer.replaceText(right, `sanitize( ${argText} )`);
129
96
  },
130
97
  }, {
131
98
  messageId: 'domPurify',
132
99
  fix: (fixer) => {
133
100
  const argText = context.sourceCode.getText(right);
134
- return fixer.replaceText(right, `DOMPurify.sanitize(${argText})`);
101
+ return fixer.replaceText(right, `DOMPurify.sanitize( ${argText} )`);
135
102
  },
136
103
  },
137
104
  ],
@@ -154,13 +121,13 @@ const plugin = {
154
121
  messageId: 'sanitize',
155
122
  fix: (fixer) => {
156
123
  const argText = context.sourceCode.getText(right);
157
- return fixer.replaceText(right, `sanitize(${argText})`);
124
+ return fixer.replaceText(right, `sanitize( ${argText} )`);
158
125
  },
159
126
  }, {
160
127
  messageId: 'domPurify',
161
128
  fix: (fixer) => {
162
129
  const argText = context.sourceCode.getText(right);
163
- return fixer.replaceText(right, `DOMPurify.sanitize(${argText})`);
130
+ return fixer.replaceText(right, `DOMPurify.sanitize( ${argText} )`);
164
131
  },
165
132
  },
166
133
  ],
@@ -173,7 +140,7 @@ const plugin = {
173
140
  if (AST_NODE_TYPES.AssignmentExpression === parent.type && parent.left === node) {
174
141
  return;
175
142
  }
176
- if (!isWindowOrLocationMemberExpression(node) || !('name' in node.property)) {
143
+ if (!isWindowOrLocation(node) || !('name' in node.property)) {
177
144
  return;
178
145
  }
179
146
  const propName = node.property.name;
@@ -1,5 +1,12 @@
1
1
  import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
2
2
  import {} from 'typescript';
3
+ /**
4
+ * Is the node of type string.
5
+ * - String literals.
6
+ * - constants of type string.
7
+ * - template literals.
8
+ * - intrinsic type string.
9
+ */
3
10
  export function isStringLike(node, context) {
4
11
  const type = getType(node, context);
5
12
  const literal = type.isStringLiteral();
@@ -9,9 +16,9 @@ export function isStringLike(node, context) {
9
16
  /**
10
17
  * Get the TypeScript type of node.
11
18
  */
12
- export function getType(arg, context) {
19
+ export function getType(expression, context) {
13
20
  const { getTypeAtLocation } = ESLintUtils.getParserServices(context);
14
- const type = getTypeAtLocation(arg);
21
+ const type = getTypeAtLocation(expression);
15
22
  return type.getNonNullableType();
16
23
  }
17
24
  /**
@@ -22,10 +29,13 @@ export function getType(arg, context) {
22
29
  *
23
30
  * @link https://typescript-eslint.io/developers/custom-rules/#typed-rules
24
31
  */
25
- export function isDomElementType(arg, context) {
26
- const element = getType(arg, context);
27
- const name = element.getSymbol()?.escapedName ?? '';
32
+ export function isDomElementType(expression, context) {
33
+ const type = getType(expression, context);
34
+ const name = type.getSymbol()?.escapedName ?? '';
28
35
  // Match any type that ends with "Element", e.g., HTMLElement, HTMLDivElement, Element, etc.
36
+ if ('Element' === name) {
37
+ return true;
38
+ }
29
39
  return name.startsWith('HTML') && name.endsWith('Element');
30
40
  }
31
41
  /**
@@ -45,3 +55,24 @@ export function isSanitized(node) {
45
55
  }
46
56
  return false;
47
57
  }
58
+ /**
59
+ * Check if a node is a literal string
60
+ */
61
+ export function isLiteralString(node) {
62
+ return AST_NODE_TYPES.Literal === node.type && 'string' === typeof node.value;
63
+ }
64
+ /**
65
+ * Check if a node is a literal string that is safe to use in an HTML context.
66
+ * - Must be a literal string.
67
+ * - Must not contain `<script`.
68
+ * - Must not start with a dangerous protocol (javascript:, data:, vbscript:, about:, livescript:).
69
+ */
70
+ export function isSafeLiteralString(node) {
71
+ if (!isLiteralString(node)) {
72
+ return false;
73
+ }
74
+ if (node.value.includes('<script')) {
75
+ return false;
76
+ }
77
+ return !/^\s*(?:javascript|data|vbscript|about|livescript)\s*:/i.test(decodeURIComponent(node.value.replace(/[\u0000-\u001F\u007F]+/g, '')));
78
+ }
@@ -1,7 +1,7 @@
1
1
  import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';
2
2
  type Plugin = FlatConfig.Plugin & {
3
3
  configs: {
4
- recommended: FlatConfig.ConfigArray;
4
+ recommended: FlatConfig.Config;
5
5
  };
6
6
  };
7
7
  declare const plugin: Plugin;
@@ -1,3 +1,4 @@
1
1
  import { type TSESLint } from '@typescript-eslint/utils';
2
- declare const plugin: TSESLint.RuleModule<'dangerousInnerHtml'>;
2
+ type Messages = 'dangerousInnerHtml' | 'sanitize' | 'domPurify';
3
+ declare const plugin: TSESLint.RuleModule<Messages>;
3
4
  export default plugin;
@@ -1,3 +1,9 @@
1
- import { type TSESLint } from '@typescript-eslint/utils';
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * Check if an expression contains any HTML-like strings.
4
+ * - Looks for `<` or `>` characters in string literals and template literals.
5
+ * - Recursively checks binary expressions with the ` + ` operator.
6
+ */
7
+ export declare function hasHtmlLikeStrings(node: TSESTree.Expression | TSESTree.PrivateIdentifier): boolean;
2
8
  declare const plugin: TSESLint.RuleModule<'htmlStringConcat'>;
3
9
  export default plugin;
@@ -1,10 +1,17 @@
1
1
  import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
2
  import { type Type } from 'typescript';
3
+ /**
4
+ * Is the node of type string.
5
+ * - String literals.
6
+ * - constants of type string.
7
+ * - template literals.
8
+ * - intrinsic type string.
9
+ */
3
10
  export declare function isStringLike(node: TSESTree.CallExpressionArgument, context: Readonly<TSESLint.RuleContext<string, readonly []>>): boolean;
4
11
  /**
5
12
  * Get the TypeScript type of node.
6
13
  */
7
- export declare function getType<Context extends Readonly<TSESLint.RuleContext<string, readonly []>>>(arg: TSESTree.CallExpressionArgument, context: Context): Type;
14
+ export declare function getType<Context extends Readonly<TSESLint.RuleContext<string, readonly []>>>(expression: TSESTree.CallExpressionArgument, context: Context): Type;
8
15
  /**
9
16
  * Is the type of variable being passed a DOM element?
10
17
  *
@@ -13,9 +20,20 @@ export declare function getType<Context extends Readonly<TSESLint.RuleContext<st
13
20
  *
14
21
  * @link https://typescript-eslint.io/developers/custom-rules/#typed-rules
15
22
  */
16
- export declare function isDomElementType<Context extends Readonly<TSESLint.RuleContext<string, readonly []>>>(arg: TSESTree.CallExpressionArgument, context: Context): boolean;
23
+ export declare function isDomElementType<Context extends Readonly<TSESLint.RuleContext<string, readonly []>>>(expression: TSESTree.CallExpressionArgument, context: Context): boolean;
17
24
  /**
18
25
  * Check if a node is a call to a known sanitization function.
19
26
  * - Currently recognizes `sanitize(...)` and `DOMPurify.sanitize(...)`.
20
27
  */
21
28
  export declare function isSanitized(node: TSESTree.Property['value'] | TSESTree.CallExpressionArgument): boolean;
29
+ /**
30
+ * Check if a node is a literal string
31
+ */
32
+ export declare function isLiteralString(node: TSESTree.Property['value'] | TSESTree.CallExpressionArgument): node is TSESTree.StringLiteral;
33
+ /**
34
+ * Check if a node is a literal string that is safe to use in an HTML context.
35
+ * - Must be a literal string.
36
+ * - Must not contain `<script`.
37
+ * - Must not start with a dangerous protocol (javascript:, data:, vbscript:, about:, livescript:).
38
+ */
39
+ export declare function isSafeLiteralString(node: TSESTree.Property['value'] | TSESTree.CallExpressionArgument): boolean;