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

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.
@@ -0,0 +1,128 @@
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
+ }
9
+ /**
10
+ * Get the callee name from a call expression
11
+ */
12
+ function getCalleeName(node) {
13
+ if (AST_NODE_TYPES.Identifier === node.callee.type) {
14
+ return node.callee.name;
15
+ }
16
+ else if (AST_NODE_TYPES.MemberExpression === node.callee.type) {
17
+ if ('name' in node.callee.object && 'name' in node.callee.property) {
18
+ return `${node.callee.object.name}.${node.callee.property.name}`;
19
+ }
20
+ else if ('name' in node.callee.property) {
21
+ return node.callee.property.name;
22
+ }
23
+ }
24
+ return '';
25
+ }
26
+ /**
27
+ * Check if the assignment is to body.style.cssText
28
+ */
29
+ function isCssTextAssignment(node) {
30
+ if (AST_NODE_TYPES.MemberExpression !== node.left.type) {
31
+ return false;
32
+ }
33
+ const memberExpr = node.left;
34
+ if (AST_NODE_TYPES.MemberExpression !== memberExpr.object.type || !('name' in memberExpr.property)) {
35
+ return false;
36
+ }
37
+ const parentMember = memberExpr.object;
38
+ if (!('name' in parentMember.object) || !('name' in parentMember.property)) {
39
+ return false;
40
+ }
41
+ return ('body' === parentMember.object.name &&
42
+ 'style' === parentMember.property.name &&
43
+ 'cssText' === memberExpr.property.name);
44
+ }
45
+ const plugin = {
46
+ defaultOptions: [],
47
+ meta: {
48
+ type: 'problem',
49
+ docs: {
50
+ description: 'Detect security issues with HTML sinks: setTimeout/setInterval with strings, unsanitized window.open, and unsanitized body.style.cssText',
51
+ },
52
+ messages: {
53
+ setTimeoutString: 'setTimeout should not receive a string. Pass a function instead.',
54
+ setIntervalString: 'setInterval should not receive a string. Pass a function instead.',
55
+ windowOpenUnsanitized: 'window.open should be sanitized to prevent XSS attacks.',
56
+ cssTextUnsanitized: 'body.style.cssText should be sanitized when not a literal string to prevent XSS attacks.',
57
+ sanitize: 'Wrap with sanitize(...)',
58
+ domPurify: 'Wrap with DOMPurify.sanitize(...)',
59
+ },
60
+ schema: [],
61
+ hasSuggestions: true,
62
+ },
63
+ create(context) {
64
+ return {
65
+ CallExpression(node) {
66
+ const calleeName = getCalleeName(node);
67
+ // Handle setTimeout and setInterval with string arguments (not allowed)
68
+ if ('setTimeout' === calleeName || 'setInterval' === calleeName) {
69
+ const firstArg = node.arguments[0];
70
+ if (isStringLike(firstArg, context)) {
71
+ context.report({
72
+ node,
73
+ messageId: 'setTimeout' === calleeName ? 'setTimeoutString' : 'setIntervalString',
74
+ });
75
+ }
76
+ return;
77
+ }
78
+ // Handle window.open with string arguments (must be sanitized)
79
+ if ('window.open' === calleeName) {
80
+ const firstArg = node.arguments[0];
81
+ if (!isSanitized(firstArg)) {
82
+ const sourceCode = context.sourceCode;
83
+ const argText = sourceCode.getText(firstArg);
84
+ context.report({
85
+ node,
86
+ messageId: 'windowOpenUnsanitized',
87
+ suggest: [
88
+ {
89
+ messageId: 'domPurify',
90
+ fix: fixer => fixer.replaceText(firstArg, `DOMPurify.sanitize(${argText})`),
91
+ },
92
+ {
93
+ messageId: 'sanitize',
94
+ fix: fixer => fixer.replaceText(firstArg, `sanitize(${argText})`),
95
+ },
96
+ ],
97
+ });
98
+ }
99
+ }
100
+ },
101
+ AssignmentExpression(node) {
102
+ // Handle body.style.cssText assignments
103
+ if (isCssTextAssignment(node)) {
104
+ // Allow literal strings but require sanitization for other values
105
+ if (!isLiteralString(node.right) && !isSanitized(node.right)) {
106
+ const sourceCode = context.sourceCode;
107
+ const rightText = sourceCode.getText(node.right);
108
+ context.report({
109
+ node,
110
+ messageId: 'cssTextUnsanitized',
111
+ suggest: [
112
+ {
113
+ messageId: 'domPurify',
114
+ fix: fixer => fixer.replaceText(node.right, `DOMPurify.sanitize(${rightText})`),
115
+ },
116
+ {
117
+ messageId: 'sanitize',
118
+ fix: fixer => fixer.replaceText(node.right, `sanitize(${rightText})`),
119
+ },
120
+ ],
121
+ });
122
+ }
123
+ }
124
+ },
125
+ };
126
+ },
127
+ };
128
+ export default plugin;
@@ -0,0 +1,58 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ function isStringConcat(node) {
3
+ // 'foo' + userInput + 'bar' (HTML-like only)
4
+ return AST_NODE_TYPES.BinaryExpression === node.type && '+' === node.operator && hasHtmlLikeStrings(node);
5
+ }
6
+ function hasHtmlLikeStrings(node) {
7
+ if (AST_NODE_TYPES.Literal === node.type && 'string' === typeof node.value) {
8
+ return /[<>]/.test(node.value);
9
+ }
10
+ if (AST_NODE_TYPES.TemplateLiteral === node.type) {
11
+ // Check any static part of the template for HTML-like content.
12
+ return node.quasis.some(q => /[<>]/.test(q.value.cooked));
13
+ }
14
+ if (AST_NODE_TYPES.BinaryExpression === node.type && '+' === node.operator) {
15
+ return hasHtmlLikeStrings(node.left) || hasHtmlLikeStrings(node.right);
16
+ }
17
+ return false;
18
+ }
19
+ const plugin = {
20
+ defaultOptions: [],
21
+ meta: {
22
+ type: 'problem',
23
+ docs: {
24
+ description: 'Disallow string concatenation with HTML-like content',
25
+ },
26
+ messages: {
27
+ htmlStringConcat: 'HTML string concatenation detected, this is a security risk, use DOM node construction or a templating language instead.',
28
+ },
29
+ schema: [],
30
+ },
31
+ create(context) {
32
+ return {
33
+ AssignmentExpression(node) {
34
+ const right = node.right;
35
+ if (isStringConcat(right)) {
36
+ context.report({
37
+ node,
38
+ messageId: 'htmlStringConcat',
39
+ });
40
+ }
41
+ },
42
+ VariableDeclarator(node) {
43
+ // Detect string concatenation assigned at declaration time
44
+ const init = node.init;
45
+ if (null === init) {
46
+ return;
47
+ }
48
+ if (isStringConcat(init)) {
49
+ context.report({
50
+ node,
51
+ messageId: 'htmlStringConcat',
52
+ });
53
+ }
54
+ },
55
+ };
56
+ },
57
+ };
58
+ export default plugin;
@@ -0,0 +1,105 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { getType, isSanitized } from '../utils/shared.js';
3
+ /**
4
+ * @link https://docs.wpvip.com/security/javascript-security-recommendations/#h-stripping-tags
5
+ */
6
+ const JQUERY_METHODS = [
7
+ 'after', 'append', 'appendTo', 'before', 'html',
8
+ 'insertAfter', 'insertBefore', 'prepend', 'prependTo',
9
+ 'replaceAll', 'replaceWith',
10
+ ];
11
+ /**
12
+ * Is the type of variable being passed a jQuery element?
13
+ *
14
+ * - jQuery elements are of type `JQuery`.
15
+ * - jQuery elements do not require sanitization.
16
+ *
17
+ * @link https://typescript-eslint.io/developers/custom-rules/#typed-rules
18
+ */
19
+ export function isJQueryElementType(arg, context) {
20
+ const element = getType(arg, context);
21
+ return 'JQuery' === element.getSymbol()?.escapedName;
22
+ }
23
+ function isJQueryMethod(methodName) {
24
+ return JQUERY_METHODS.includes(methodName);
25
+ }
26
+ export function isJQueryCall(node) {
27
+ if (AST_NODE_TYPES.MemberExpression !== node.callee.type || !('name' in node.callee.property)) {
28
+ return false;
29
+ }
30
+ // Walk to the root object of the call chain
31
+ let obj = node.callee.object ?? null;
32
+ if (null === obj) {
33
+ return false;
34
+ }
35
+ while (AST_NODE_TYPES.MemberExpression === obj.type) {
36
+ obj = obj.object;
37
+ }
38
+ return (AST_NODE_TYPES.CallExpression === obj.type && AST_NODE_TYPES.Identifier === obj.callee.type &&
39
+ ('$' === obj.callee.name || 'jQuery' === obj.callee.name));
40
+ }
41
+ export function getJQueryCall(node) {
42
+ // Detect $(...).method(userInput) or jQuery(...).method(...)
43
+ if (AST_NODE_TYPES.MemberExpression !== node.callee.type || !('name' in node.callee.property)) {
44
+ return null;
45
+ }
46
+ const methodName = node.callee.property.name;
47
+ if (!isJQueryMethod(methodName)) {
48
+ return null;
49
+ }
50
+ return isJQueryCall(node) ? methodName : null;
51
+ }
52
+ const plugin = {
53
+ defaultOptions: [],
54
+ meta: {
55
+ docs: {
56
+ description: 'Disallow using unsanitized values in jQuery methods that execute HTML',
57
+ },
58
+ fixable: 'code',
59
+ hasSuggestions: true,
60
+ messages: {
61
+ needsEscaping: 'Any HTML used with `{{methodName}}` gets executed. Make sure it\'s properly escaped.',
62
+ // Suggestions
63
+ domPurify: 'Wrap the argument with a `DOMPurify.sanitize()` call.',
64
+ sanitize: 'Wrap the argument with a `sanitize()` call.',
65
+ },
66
+ schema: [],
67
+ type: 'problem',
68
+ },
69
+ create(context) {
70
+ return {
71
+ CallExpression(node) {
72
+ const methodName = getJQueryCall(node);
73
+ if (null !== methodName) {
74
+ const arg = node.arguments[0];
75
+ if (!isSanitized(arg) && !isJQueryElementType(arg, context)) {
76
+ context.report({
77
+ node,
78
+ messageId: 'needsEscaping',
79
+ data: {
80
+ methodName,
81
+ },
82
+ suggest: [
83
+ {
84
+ messageId: 'domPurify',
85
+ fix: (fixer) => {
86
+ const argText = context.sourceCode.getText(arg);
87
+ return fixer.replaceText(arg, `DOMPurify.sanitize(${argText})`);
88
+ },
89
+ },
90
+ {
91
+ messageId: 'sanitize',
92
+ fix: (fixer) => {
93
+ const argText = context.sourceCode.getText(arg);
94
+ return fixer.replaceText(arg, `sanitize(${argText})`);
95
+ },
96
+ },
97
+ ],
98
+ });
99
+ }
100
+ }
101
+ },
102
+ };
103
+ },
104
+ };
105
+ export default plugin;
@@ -0,0 +1,76 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { isJQueryCall } from './jquery-executing.js';
3
+ /**
4
+ * Detects a vulnerable tag stripping pattern: .html(value).text()
5
+ *
6
+ * This pattern can lead to XSS vulnerabilities when HTML is inserted and then text is extracted.
7
+ *
8
+ * @link https://docs.wpvip.com/security/javascript-security-recommendations/#h-stripping-tags
9
+ */
10
+ function isTextAfterHtml(node) {
11
+ // Check if this is a .text() call
12
+ if (AST_NODE_TYPES.MemberExpression !== node.callee.type || !('name' in node.callee.property)) {
13
+ return null;
14
+ }
15
+ if (node.callee.property.name !== 'text') {
16
+ return null;
17
+ }
18
+ const parentCall = node.callee.object;
19
+ if (AST_NODE_TYPES.CallExpression !== parentCall.type) {
20
+ return null;
21
+ }
22
+ if (AST_NODE_TYPES.MemberExpression !== parentCall.callee.type || !('name' in parentCall.callee.property)) {
23
+ return null;
24
+ }
25
+ if (parentCall.callee.property.name !== 'html') {
26
+ return null;
27
+ }
28
+ return isJQueryCall(parentCall) ? parentCall.callee : null;
29
+ }
30
+ const plugin = {
31
+ defaultOptions: [],
32
+ meta: {
33
+ docs: {
34
+ description: 'Disallow jQuery .html().text() chaining which can lead to XSS through tag stripping',
35
+ },
36
+ fixable: 'code',
37
+ hasSuggestions: true,
38
+ messages: {
39
+ vulnerableTagStripping: 'Using .html().text() can lead to XSS vulnerabilities through tag stripping. Use only .text()',
40
+ useTextOnly: 'Remove .html() and move the argument to .text()',
41
+ },
42
+ schema: [],
43
+ type: 'problem',
44
+ },
45
+ create(context) {
46
+ return {
47
+ CallExpression(node) {
48
+ const htmlProperty = isTextAfterHtml(node);
49
+ if (null === htmlProperty) {
50
+ return;
51
+ }
52
+ const jquerySelector = htmlProperty.object;
53
+ const parentCall = htmlProperty.parent;
54
+ if (AST_NODE_TYPES.CallExpression !== parentCall.type) {
55
+ return;
56
+ }
57
+ const htmlArg = parentCall.arguments[0];
58
+ context.report({
59
+ node,
60
+ messageId: 'vulnerableTagStripping',
61
+ suggest: [
62
+ {
63
+ messageId: 'useTextOnly',
64
+ fix: (fixer) => {
65
+ const selectorText = context.sourceCode.getText(jquerySelector);
66
+ const argText = context.sourceCode.getText(htmlArg);
67
+ return fixer.replaceText(node, `${selectorText}.text( ${argText} )`);
68
+ },
69
+ },
70
+ ],
71
+ });
72
+ },
73
+ };
74
+ },
75
+ };
76
+ export default plugin;
@@ -0,0 +1,213 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { isSanitized } from '../utils/shared.js';
3
+ // Window and location properties that need special handling
4
+ const LOCATION_PROPS = new Set(['href', 'src', 'action',
5
+ 'protocol', 'host', 'hostname', 'pathname', 'search', 'hash', 'username', 'port', 'name', 'status',
6
+ ]);
7
+ const WINDOW_PROPS = new Set(['name', 'status']);
8
+ export function isSafeUrlString(value) {
9
+ return !/^\s*(?:javascript|data|vbscript|about|livescript)\s*:/i.test(decodeURIComponent(value.replace(/[\u0000-\u001F\u007F]+/g, '')));
10
+ }
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);
43
+ }
44
+ function isWindowLocationAssignment(node) {
45
+ // window.location.<prop> = ...
46
+ return (AST_NODE_TYPES.MemberExpression === node.left.type &&
47
+ AST_NODE_TYPES.MemberExpression === node.left.object.type &&
48
+ AST_NODE_TYPES.Identifier === node.left.object.property.type &&
49
+ AST_NODE_TYPES.Identifier === node.left.object.object.type &&
50
+ AST_NODE_TYPES.Identifier === node.left.property.type &&
51
+ 'window' === node.left.object.object.name &&
52
+ 'location' === node.left.object.property.name &&
53
+ LOCATION_PROPS.has(node.left.property.name));
54
+ }
55
+ function isWindowAssignment(node) {
56
+ // window.<prop> = ...
57
+ return (AST_NODE_TYPES.MemberExpression === node.left.type &&
58
+ AST_NODE_TYPES.Identifier === node.left.object.type &&
59
+ AST_NODE_TYPES.Identifier === node.left.property.type &&
60
+ 'window' === node.left.object.name &&
61
+ WINDOW_PROPS.has(node.left.property.name));
62
+ }
63
+ function isWindowOrLocationMemberExpression(memberExpr) {
64
+ // Helper to detect a window.* or window.location.*
65
+ if (AST_NODE_TYPES.MemberExpression !== memberExpr.type) {
66
+ return false;
67
+ }
68
+ if (AST_NODE_TYPES.Identifier === memberExpr.object.type && 'window' === memberExpr.object.name) {
69
+ return true;
70
+ }
71
+ if (AST_NODE_TYPES.MemberExpression === memberExpr.object.type) {
72
+ const memberObject = memberExpr.object;
73
+ const isObjectWindow = AST_NODE_TYPES.Identifier === memberObject.object.type && 'window' === memberObject.object.name;
74
+ const isPropertyLocation = AST_NODE_TYPES.Identifier === memberObject.property.type && 'location' === memberObject.property.name;
75
+ return isObjectWindow && isPropertyLocation;
76
+ }
77
+ return false;
78
+ }
79
+ const plugin = {
80
+ defaultOptions: [],
81
+ meta: {
82
+ type: 'problem',
83
+ docs: {
84
+ description: 'Require proper escaping for the window and location property access',
85
+ },
86
+ messages: {
87
+ unsafeWindow: 'Assignment to "{{propName}}" must be sanitized.',
88
+ unsafeWindowLocation: 'Assignment to window.location.{{propName}} must be sanitized.',
89
+ unsafeRead: 'Data from JS global {{propName}} may contain user-supplied values and should be sanitized before output to prevent XSS.',
90
+ // Suggestions
91
+ domPurify: 'Wrap the argument with a `DOMPurify.sanitize()` call.',
92
+ sanitize: 'Wrap the argument with a `sanitize()` call.',
93
+ },
94
+ schema: [],
95
+ hasSuggestions: true,
96
+ },
97
+ create(context) {
98
+ return {
99
+ AssignmentExpression(node) {
100
+ const right = node.right;
101
+ if (AST_NODE_TYPES.MemberExpression !== node.left.type || !('name' in node.left.property)) {
102
+ return;
103
+ }
104
+ const propName = node.left.property.name;
105
+ // window.location.<prop> = ...
106
+ if (isWindowLocationAssignment(node)) {
107
+ const rhsResolved = right;
108
+ if (!LOCATION_PROPS.has(propName)) {
109
+ return;
110
+ }
111
+ if (isSafeUrlLiteral(rhsResolved) || isSafeUrlTemplate(rhsResolved)) {
112
+ return;
113
+ }
114
+ if (isSanitized(rhsResolved)) {
115
+ return;
116
+ }
117
+ context.report({
118
+ node,
119
+ messageId: 'unsafeWindowLocation',
120
+ data: {
121
+ propName,
122
+ },
123
+ suggest: [
124
+ {
125
+ messageId: 'sanitize',
126
+ fix: (fixer) => {
127
+ const argText = context.sourceCode.getText(right);
128
+ return fixer.replaceText(right, `sanitize(${argText})`);
129
+ },
130
+ }, {
131
+ messageId: 'domPurify',
132
+ fix: (fixer) => {
133
+ const argText = context.sourceCode.getText(right);
134
+ return fixer.replaceText(right, `DOMPurify.sanitize(${argText})`);
135
+ },
136
+ },
137
+ ],
138
+ });
139
+ return;
140
+ }
141
+ // window.<prop> = ...
142
+ if (isWindowAssignment(node)) {
143
+ if (isSanitized(node.right)) {
144
+ return;
145
+ }
146
+ context.report({
147
+ node,
148
+ messageId: 'unsafeWindow',
149
+ data: {
150
+ propName,
151
+ },
152
+ suggest: [
153
+ {
154
+ messageId: 'sanitize',
155
+ fix: (fixer) => {
156
+ const argText = context.sourceCode.getText(right);
157
+ return fixer.replaceText(right, `sanitize(${argText})`);
158
+ },
159
+ }, {
160
+ messageId: 'domPurify',
161
+ fix: (fixer) => {
162
+ const argText = context.sourceCode.getText(right);
163
+ return fixer.replaceText(right, `DOMPurify.sanitize(${argText})`);
164
+ },
165
+ },
166
+ ],
167
+ });
168
+ }
169
+ },
170
+ // Check for reading from the window.location properties
171
+ MemberExpression(node) {
172
+ const parent = node.parent;
173
+ if (AST_NODE_TYPES.AssignmentExpression === parent.type && parent.left === node) {
174
+ return;
175
+ }
176
+ if (!isWindowOrLocationMemberExpression(node) || !('name' in node.property)) {
177
+ return;
178
+ }
179
+ const propName = node.property.name;
180
+ if (!LOCATION_PROPS.has(propName)) {
181
+ return;
182
+ }
183
+ if (AST_NODE_TYPES.CallExpression === parent.type && isSanitized(parent)) {
184
+ return;
185
+ }
186
+ context.report({
187
+ node,
188
+ messageId: 'unsafeRead',
189
+ data: {
190
+ propName,
191
+ },
192
+ suggest: [
193
+ {
194
+ messageId: 'sanitize',
195
+ fix: (fixer) => {
196
+ const argText = context.sourceCode.getText(node);
197
+ return fixer.replaceText(node, `sanitize( ${argText} )`);
198
+ },
199
+ },
200
+ {
201
+ messageId: 'domPurify',
202
+ fix: (fixer) => {
203
+ const argText = context.sourceCode.getText(node);
204
+ return fixer.replaceText(node, `DOMPurify.sanitize( ${argText} )`);
205
+ },
206
+ },
207
+ ],
208
+ });
209
+ },
210
+ };
211
+ },
212
+ };
213
+ export default plugin;
@@ -0,0 +1,47 @@
1
+ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
2
+ import {} from 'typescript';
3
+ export function isStringLike(node, context) {
4
+ const type = getType(node, context);
5
+ const literal = type.isStringLiteral();
6
+ const intrinsic = 'intrinsicName' in type && 'string' === type.intrinsicName;
7
+ return (AST_NODE_TYPES.Literal === node.type && 'string' === typeof node.value) || (AST_NODE_TYPES.TemplateLiteral === node.type) || literal || intrinsic;
8
+ }
9
+ /**
10
+ * Get the TypeScript type of node.
11
+ */
12
+ export function getType(arg, context) {
13
+ const { getTypeAtLocation } = ESLintUtils.getParserServices(context);
14
+ const type = getTypeAtLocation(arg);
15
+ return type.getNonNullableType();
16
+ }
17
+ /**
18
+ * Is the type of variable being passed a DOM element?
19
+ *
20
+ * - DOM elements are of the type `HTML{*}Element`.
21
+ * - DOM elements do not require sanitization.
22
+ *
23
+ * @link https://typescript-eslint.io/developers/custom-rules/#typed-rules
24
+ */
25
+ export function isDomElementType(arg, context) {
26
+ const element = getType(arg, context);
27
+ const name = element.getSymbol()?.escapedName ?? '';
28
+ // Match any type that ends with "Element", e.g., HTMLElement, HTMLDivElement, Element, etc.
29
+ return name.startsWith('HTML') && name.endsWith('Element');
30
+ }
31
+ /**
32
+ * Check if a node is a call to a known sanitization function.
33
+ * - Currently recognizes `sanitize(...)` and `DOMPurify.sanitize(...)`.
34
+ */
35
+ export function isSanitized(node) {
36
+ if (AST_NODE_TYPES.CallExpression !== node.type) {
37
+ return false;
38
+ }
39
+ if (AST_NODE_TYPES.Identifier === node.callee.type && 'sanitize' === node.callee.name) {
40
+ return true;
41
+ }
42
+ if (AST_NODE_TYPES.MemberExpression === node.callee.type && AST_NODE_TYPES.Identifier === node.callee.object.type) {
43
+ return 'dompurify' === node.callee.object.name.toLowerCase() &&
44
+ AST_NODE_TYPES.Identifier === node.callee.property.type && 'sanitize' === node.callee.property.name;
45
+ }
46
+ return false;
47
+ }
@@ -0,0 +1,22 @@
1
+ import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';
2
+ /**
3
+ * Get a config from our /index.js merged with any
4
+ * matching configuration from the project directory.
5
+ *
6
+ * For instance, if we have a file named config/eslint.config.js in our project
7
+ * we will merge the contents with our config/eslint.config.js in favor of whatever
8
+ * is specified with the project's file.
9
+ *
10
+ * If the `module.exports` are a function, the existing configuration will be passed
11
+ * as the only argument. Otherwise, standard `module.exports` are also supported.
12
+ *
13
+ * @see @lipemat/js-boilerplate/helpers/config
14
+ *
15
+ * @example ```ts
16
+ * // function
17
+ * module.exports = function( config: { configs: Linter.Config[] } ) {
18
+ * config.configs[0].push({extra: 'Extra'});
19
+ * return config
20
+ * }
21
+ */
22
+ export declare function getConfig(configs: FlatConfig.Config[]): FlatConfig.Config[];
@@ -0,0 +1,3 @@
1
+ import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';
2
+ declare const _default: (FlatConfig.Config | import("eslint").Linter.Config<import("eslint").Linter.RulesRecord>)[];
3
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';
2
+ type Plugin = FlatConfig.Plugin & {
3
+ configs: {
4
+ recommended: FlatConfig.ConfigArray;
5
+ };
6
+ };
7
+ declare const plugin: Plugin;
8
+ export default plugin;
@@ -0,0 +1,3 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ declare const plugin: TSESLint.RuleModule<'dangerousInnerHtml'>;
3
+ export default plugin;
@@ -0,0 +1,4 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Messages = 'executed' | 'sanitize' | 'domPurify';
3
+ declare const plugin: TSESLint.RuleModule<Messages>;
4
+ export default plugin;