@murky-web/oxlint-plugin-solid 0.0.1

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,217 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+
3
+ import { getScope, getSourceCode } from "../compat.mjs";
4
+ import {
5
+ isDOMElementName,
6
+ formatList,
7
+ appendImports,
8
+ insertImports,
9
+ } from "../utils.mjs";
10
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
11
+ // Currently all of the control flow components are from 'solid-js'.
12
+ const AUTO_COMPONENTS = ["Show", "For", "Index", "Switch", "Match"];
13
+ const SOURCE_MODULE = "solid-js";
14
+ export default createRule({
15
+ meta: {
16
+ type: "problem",
17
+ docs: {
18
+ description:
19
+ "Disallow references to undefined variables in JSX. Handles custom directives.",
20
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-undef.md",
21
+ },
22
+ fixable: "code",
23
+ schema: [
24
+ {
25
+ type: "object",
26
+ properties: {
27
+ allowGlobals: {
28
+ type: "boolean",
29
+ description:
30
+ "When true, the rule will consider the global scope when checking for defined components.",
31
+ default: false,
32
+ },
33
+ autoImport: {
34
+ type: "boolean",
35
+ description:
36
+ 'Automatically import certain components from `"solid-js"` if they are undefined.',
37
+ default: true,
38
+ },
39
+ typescriptEnabled: {
40
+ type: "boolean",
41
+ description:
42
+ "Adjusts behavior not to conflict with TypeScript's type checking.",
43
+ default: false,
44
+ },
45
+ },
46
+ additionalProperties: false,
47
+ },
48
+ ],
49
+ messages: {
50
+ undefined: "'{{identifier}}' is not defined.",
51
+ customDirectiveUndefined:
52
+ "Custom directive '{{identifier}}' is not defined.",
53
+ autoImport: "{{imports}} should be imported from '{{source}}'.",
54
+ },
55
+ },
56
+ defaultOptions: [],
57
+ create(context) {
58
+ const allowGlobals = context.options[0]?.allowGlobals ?? false;
59
+ const autoImport = context.options[0]?.autoImport !== false;
60
+ const isTypeScriptEnabled =
61
+ context.options[0]?.typescriptEnabled ?? false;
62
+ const missingComponentsSet = new Set();
63
+ /**
64
+ * Compare an identifier with the variables declared in the scope
65
+ * @param {ASTNode} node - Identifier or JSXIdentifier node
66
+ * @returns {void}
67
+ */
68
+ function checkIdentifierInJSX(
69
+ node,
70
+ { isComponent, isCustomDirective } = {},
71
+ ) {
72
+ let scope = getScope(context, node);
73
+ const sourceCode = getSourceCode(context);
74
+ const sourceType = sourceCode.ast.sourceType;
75
+ const scopeUpperBound =
76
+ !allowGlobals && sourceType === "module" ? "module" : "global";
77
+ const variables = [...scope.variables];
78
+ // Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX)
79
+ if (node.name === "this") {
80
+ return;
81
+ }
82
+ while (
83
+ scope.type !== scopeUpperBound &&
84
+ scope.type !== "global" &&
85
+ scope.upper
86
+ ) {
87
+ scope = scope.upper;
88
+ variables.push(...scope.variables);
89
+ }
90
+ if (scope.childScopes.length) {
91
+ variables.push(...scope.childScopes[0].variables);
92
+ // Temporary fix for babel-eslint
93
+ if (scope.childScopes[0].childScopes.length) {
94
+ variables.push(
95
+ ...scope.childScopes[0].childScopes[0].variables,
96
+ );
97
+ }
98
+ }
99
+ if (variables.find((variable) => variable.name === node.name)) {
100
+ return;
101
+ }
102
+ if (
103
+ isComponent &&
104
+ autoImport &&
105
+ AUTO_COMPONENTS.includes(node.name) &&
106
+ !missingComponentsSet.has(node.name)
107
+ ) {
108
+ // track which names are undefined
109
+ missingComponentsSet.add(node.name);
110
+ } else if (isCustomDirective) {
111
+ context.report({
112
+ node,
113
+ messageId: "customDirectiveUndefined",
114
+ data: {
115
+ identifier: node.name,
116
+ },
117
+ });
118
+ } else if (!isTypeScriptEnabled) {
119
+ context.report({
120
+ node,
121
+ messageId: "undefined",
122
+ data: {
123
+ identifier: node.name,
124
+ },
125
+ });
126
+ }
127
+ }
128
+ return {
129
+ JSXOpeningElement(node) {
130
+ let n;
131
+ switch (node.name.type) {
132
+ case "JSXIdentifier":
133
+ if (!isDOMElementName(node.name.name)) {
134
+ checkIdentifierInJSX(node.name, {
135
+ isComponent: true,
136
+ });
137
+ }
138
+ break;
139
+ case "JSXMemberExpression":
140
+ n = node.name;
141
+ do {
142
+ n = n.object;
143
+ } while (n && n.type !== "JSXIdentifier");
144
+ if (n) {
145
+ checkIdentifierInJSX(n);
146
+ }
147
+ break;
148
+ default:
149
+ break;
150
+ }
151
+ },
152
+ "JSXAttribute > JSXNamespacedName": (node) => {
153
+ // <Element use:X /> applies the `X` custom directive to the element, where `X` must be an identifier in scope.
154
+ if (
155
+ node.namespace?.type === "JSXIdentifier" &&
156
+ node.namespace.name === "use" &&
157
+ node.name?.type === "JSXIdentifier"
158
+ ) {
159
+ checkIdentifierInJSX(node.name, {
160
+ isCustomDirective: true,
161
+ });
162
+ }
163
+ },
164
+ "Program:exit": (programNode) => {
165
+ // add in any auto import components used in the program
166
+ const missingComponents = Array.from(
167
+ missingComponentsSet.values(),
168
+ );
169
+ if (autoImport && missingComponents.length) {
170
+ const importNode = programNode.body.find(
171
+ (n) =>
172
+ n.type === "ImportDeclaration" &&
173
+ n.importKind !== "type" &&
174
+ n.source.type === "Literal" &&
175
+ n.source.value === SOURCE_MODULE,
176
+ );
177
+ if (importNode) {
178
+ context.report({
179
+ node: importNode,
180
+ messageId: "autoImport",
181
+ data: {
182
+ imports: formatList(missingComponents), // "Show, For, and Switch"
183
+ source: SOURCE_MODULE,
184
+ },
185
+ fix: (fixer) => {
186
+ return appendImports(
187
+ fixer,
188
+ getSourceCode(context),
189
+ importNode,
190
+ missingComponents,
191
+ );
192
+ },
193
+ });
194
+ } else {
195
+ context.report({
196
+ node: programNode,
197
+ messageId: "autoImport",
198
+ data: {
199
+ imports: formatList(missingComponents),
200
+ source: SOURCE_MODULE,
201
+ },
202
+ fix: (fixer) => {
203
+ // insert `import { missing, identifiers } from "solid-js"` at top of module
204
+ return insertImports(
205
+ fixer,
206
+ getSourceCode(context),
207
+ "solid-js",
208
+ missingComponents,
209
+ );
210
+ },
211
+ });
212
+ }
213
+ }
214
+ },
215
+ };
216
+ },
217
+ });
@@ -0,0 +1,55 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+
3
+ import { markVariableAsUsed } from "../compat.mjs";
4
+
5
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
6
+
7
+ export const jsxUsesVarsRule = createRule({
8
+ meta: {
9
+ docs: {
10
+ description:
11
+ "Prevent variables used in JSX from being marked as unused.",
12
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-uses-vars.md",
13
+ },
14
+ messages: {},
15
+ schema: [],
16
+ type: "problem",
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ return {
21
+ JSXOpeningElement(node) {
22
+ let parent = null;
23
+
24
+ switch (node.name.type) {
25
+ case "JSXNamespacedName":
26
+ return;
27
+ case "JSXIdentifier":
28
+ markVariableAsUsed(context, node.name.name, node.name);
29
+ return;
30
+ case "JSXMemberExpression":
31
+ parent = node.name.object;
32
+ while (parent?.type === "JSXMemberExpression") {
33
+ parent = parent.object;
34
+ }
35
+
36
+ if (parent?.type === "JSXIdentifier") {
37
+ markVariableAsUsed(context, parent.name, parent);
38
+ }
39
+ return;
40
+ default:
41
+ return;
42
+ }
43
+ },
44
+ "JSXAttribute > JSXNamespacedName"(node) {
45
+ if (
46
+ node.namespace?.type === "JSXIdentifier" &&
47
+ node.namespace.name === "use" &&
48
+ node.name?.type === "JSXIdentifier"
49
+ ) {
50
+ markVariableAsUsed(context, node.name.name, node.name);
51
+ }
52
+ },
53
+ };
54
+ },
55
+ });
@@ -0,0 +1,53 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+
3
+ import { isDOMElementName, trace } from "../utils.mjs";
4
+
5
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
6
+
7
+ export const noArrayHandlersRule = createRule({
8
+ meta: {
9
+ docs: {
10
+ description: "Disallow usage of type-unsafe event handlers.",
11
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-array-handlers.md",
12
+ },
13
+ messages: {
14
+ noArrayHandlers:
15
+ "Passing an array as an event handler is potentially type-unsafe.",
16
+ },
17
+ schema: [],
18
+ type: "problem",
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ JSXAttribute(node) {
24
+ const openingElement = node.parent;
25
+ if (
26
+ openingElement.name.type !== "JSXIdentifier" ||
27
+ !isDOMElementName(openingElement.name.name)
28
+ ) {
29
+ return;
30
+ }
31
+
32
+ const isNamespacedHandler =
33
+ node.name.type === "JSXNamespacedName" &&
34
+ node.name.namespace.name === "on";
35
+ const isNormalEventHandler =
36
+ node.name.type === "JSXIdentifier" &&
37
+ /^on[a-zA-Z]/.test(node.name.name);
38
+
39
+ if (
40
+ (isNamespacedHandler || isNormalEventHandler) &&
41
+ node.value?.type === "JSXExpressionContainer" &&
42
+ trace(node.value.expression, context).type ===
43
+ "ArrayExpression"
44
+ ) {
45
+ context.report({
46
+ messageId: "noArrayHandlers",
47
+ node,
48
+ });
49
+ }
50
+ },
51
+ };
52
+ },
53
+ });
@@ -0,0 +1,210 @@
1
+ import { ESLintUtils, ASTUtils } from "@typescript-eslint/utils";
2
+
3
+ import { getSourceCode } from "../compat.mjs";
4
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
5
+ const { getStringIfConstant } = ASTUtils;
6
+ const getName = (node) => {
7
+ switch (node.type) {
8
+ case "Literal":
9
+ return typeof node.value === "string" ? node.value : null;
10
+ case "Identifier":
11
+ return node.name;
12
+ case "AssignmentPattern":
13
+ return getName(node.left);
14
+ default:
15
+ return getStringIfConstant(node);
16
+ }
17
+ };
18
+ // Given ({ 'hello-world': helloWorld = 5 }), returns { real: Literal('hello-world'), var: 'helloWorld', computed: false, init: Literal(5) }
19
+ const getPropertyInfo = (prop) => {
20
+ const valueName = getName(prop.value);
21
+ if (valueName !== null) {
22
+ return {
23
+ real: prop.key,
24
+ var: valueName,
25
+ computed: prop.computed,
26
+ init:
27
+ prop.value.type === "AssignmentPattern"
28
+ ? prop.value.right
29
+ : undefined,
30
+ };
31
+ } else {
32
+ return null;
33
+ }
34
+ };
35
+ export default createRule({
36
+ meta: {
37
+ type: "problem",
38
+ docs: {
39
+ description:
40
+ "Disallow destructuring props. In Solid, props must be used with property accesses (`props.foo`) to preserve reactivity. This rule only tracks destructuring in the parameter list.",
41
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-destructure.md",
42
+ },
43
+ fixable: "code",
44
+ schema: [],
45
+ messages: {
46
+ noDestructure:
47
+ "Destructuring component props breaks Solid's reactivity; use property access instead.",
48
+ // noWriteToProps: "Component props are readonly, writing to props is not supported.",
49
+ },
50
+ },
51
+ defaultOptions: [],
52
+ create(context) {
53
+ const functionStack = [];
54
+ const currentFunction = () => functionStack[functionStack.length - 1];
55
+ const onFunctionEnter = () => {
56
+ functionStack.push({ hasJSX: false });
57
+ };
58
+ const onFunctionExit = (node) => {
59
+ if (node.params.length === 1) {
60
+ const props = node.params[0];
61
+ if (
62
+ props.type === "ObjectPattern" &&
63
+ currentFunction().hasJSX &&
64
+ node.parent?.type !== "JSXExpressionContainer" // "render props" aren't components
65
+ ) {
66
+ // Props are destructured in the function params, not the body. We actually don't
67
+ // need to handle the case where props are destructured in the body, because that
68
+ // will be a violation of "solid/reactivity".
69
+ context.report({
70
+ node: props,
71
+ messageId: "noDestructure",
72
+ fix: (fixer) => fixDestructure(node, props, fixer),
73
+ });
74
+ }
75
+ }
76
+ // Pop on exit
77
+ functionStack.pop();
78
+ };
79
+ function* fixDestructure(func, props, fixer) {
80
+ const propsName = "props";
81
+ const properties = props.properties;
82
+ const propertyInfo = [];
83
+ let rest = null;
84
+ for (const property of properties) {
85
+ if (property.type === "RestElement") {
86
+ rest = property;
87
+ } else {
88
+ const info = getPropertyInfo(property);
89
+ if (info === null) {
90
+ continue;
91
+ }
92
+ propertyInfo.push(info);
93
+ }
94
+ }
95
+ const hasDefaults = propertyInfo.some((info) => info.init);
96
+ // Replace destructured props with a `props` identifier (`_props` in case of rest params/defaults)
97
+ const origProps = !(hasDefaults || rest)
98
+ ? propsName
99
+ : "_" + propsName;
100
+ if (props.typeAnnotation) {
101
+ // in `{ prop1, prop2 }: Props`, leave `: Props` alone
102
+ const range = [props.range[0], props.typeAnnotation.range[0]];
103
+ yield fixer.replaceTextRange(range, origProps);
104
+ } else {
105
+ yield fixer.replaceText(props, origProps);
106
+ }
107
+ const sourceCode = getSourceCode(context);
108
+ const defaultsObjectString = () =>
109
+ propertyInfo
110
+ .filter((info) => info.init)
111
+ .map(
112
+ (info) =>
113
+ `${info.computed ? "[" : ""}${sourceCode.getText(info.real)}${info.computed ? "]" : ""}: ${sourceCode.getText(info.init)}`,
114
+ )
115
+ .join(", ");
116
+ const splitPropsArray = () =>
117
+ `[${propertyInfo
118
+ .map((info) =>
119
+ info.real.type === "Identifier"
120
+ ? JSON.stringify(info.real.name)
121
+ : sourceCode.getText(info.real),
122
+ )
123
+ .join(", ")}]`;
124
+ let lineToInsert = "";
125
+ if (hasDefaults && rest) {
126
+ // Insert a line that assigns _props
127
+ lineToInsert = ` const [${propsName}, ${(rest.argument.type === "Identifier" && rest.argument.name) || "rest"}] = splitProps(mergeProps({ ${defaultsObjectString()} }, ${origProps}), ${splitPropsArray()});`;
128
+ } else if (hasDefaults) {
129
+ // Insert a line that assigns _props merged with defaults to props
130
+ lineToInsert = ` const ${propsName} = mergeProps({ ${defaultsObjectString()} }, ${origProps});\n`;
131
+ } else if (rest) {
132
+ // Insert a line that keeps named props and extracts the rest into a new reactive rest object
133
+ lineToInsert = ` const [${propsName}, ${(rest.argument.type === "Identifier" && rest.argument.name) || "rest"}] = splitProps(${origProps}, ${splitPropsArray()});\n`;
134
+ }
135
+ if (lineToInsert) {
136
+ const body = func.body;
137
+ if (body.type === "BlockStatement") {
138
+ if (body.body.length > 0) {
139
+ // Inject lines handling defaults/rest params before the first statement in the block.
140
+ yield fixer.insertTextBefore(
141
+ body.body[0],
142
+ lineToInsert,
143
+ );
144
+ }
145
+ // with an empty block statement body, no need to inject code
146
+ } else {
147
+ // The function is an arrow function that implicitly returns an expression, possibly with wrapping parentheses.
148
+ // These must be removed to convert the function body to a block statement for code injection.
149
+ const maybeOpenParen = sourceCode.getTokenBefore(body);
150
+ if (maybeOpenParen?.value === "(") {
151
+ yield fixer.remove(maybeOpenParen);
152
+ }
153
+ const maybeCloseParen = sourceCode.getTokenAfter(body);
154
+ if (maybeCloseParen?.value === ")") {
155
+ yield fixer.remove(maybeCloseParen);
156
+ }
157
+ // Inject lines handling defaults/rest params
158
+ yield fixer.insertTextBefore(
159
+ body,
160
+ `{\n${lineToInsert} return (`,
161
+ );
162
+ yield fixer.insertTextAfter(body, `);\n}`);
163
+ }
164
+ }
165
+ const scope = sourceCode.scopeManager?.acquire(func);
166
+ if (scope) {
167
+ // iterate through destructured variables, associated with real node
168
+ for (const [info, variable] of propertyInfo.map((info) => [
169
+ info,
170
+ scope.set.get(info.var),
171
+ ])) {
172
+ if (variable) {
173
+ // replace all usages of the variable with props accesses
174
+ for (const reference of variable.references) {
175
+ if (reference.isReadOnly()) {
176
+ const access =
177
+ info.real.type === "Identifier" &&
178
+ !info.computed
179
+ ? `.${info.real.name}`
180
+ : `[${sourceCode.getText(info.real)}]`;
181
+ yield fixer.replaceText(
182
+ reference.identifier,
183
+ `${propsName}${access}`,
184
+ );
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ return {
192
+ FunctionDeclaration: onFunctionEnter,
193
+ FunctionExpression: onFunctionEnter,
194
+ ArrowFunctionExpression: onFunctionEnter,
195
+ "FunctionDeclaration:exit": onFunctionExit,
196
+ "FunctionExpression:exit": onFunctionExit,
197
+ "ArrowFunctionExpression:exit": onFunctionExit,
198
+ JSXElement() {
199
+ if (functionStack.length) {
200
+ currentFunction().hasJSX = true;
201
+ }
202
+ },
203
+ JSXFragment() {
204
+ if (functionStack.length) {
205
+ currentFunction().hasJSX = true;
206
+ }
207
+ },
208
+ };
209
+ },
210
+ });
@@ -0,0 +1,145 @@
1
+ import { ESLintUtils, ASTUtils } from "@typescript-eslint/utils";
2
+ import isHtml from "is-html";
3
+
4
+ import { jsxPropName } from "../utils.mjs";
5
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
6
+ const { getStringIfConstant } = ASTUtils;
7
+ export default createRule({
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description:
12
+ "Disallow usage of the innerHTML attribute, which can often lead to security vulnerabilities.",
13
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-innerhtml.md",
14
+ },
15
+ fixable: "code",
16
+ hasSuggestions: true,
17
+ schema: [
18
+ {
19
+ type: "object",
20
+ properties: {
21
+ allowStatic: {
22
+ description:
23
+ "if the innerHTML value is guaranteed to be a static HTML string (i.e. no user input), allow it",
24
+ type: "boolean",
25
+ default: true,
26
+ },
27
+ },
28
+ additionalProperties: false,
29
+ },
30
+ ],
31
+ messages: {
32
+ dangerous:
33
+ "The innerHTML attribute is dangerous; passing unsanitized input can lead to security vulnerabilities.",
34
+ conflict:
35
+ "The innerHTML attribute should not be used on an element with child elements; they will be overwritten.",
36
+ notHtml:
37
+ "The string passed to innerHTML does not appear to be valid HTML.",
38
+ useInnerText:
39
+ "For text content, using innerText is clearer and safer.",
40
+ dangerouslySetInnerHTML:
41
+ "The dangerouslySetInnerHTML prop is not supported; use innerHTML instead.",
42
+ },
43
+ },
44
+ defaultOptions: [{ allowStatic: true }],
45
+ create(context) {
46
+ const allowStatic = Boolean(context.options[0]?.allowStatic ?? true);
47
+ return {
48
+ JSXAttribute(node) {
49
+ if (jsxPropName(node) === "dangerouslySetInnerHTML") {
50
+ if (
51
+ node.value?.type === "JSXExpressionContainer" &&
52
+ node.value.expression.type === "ObjectExpression" &&
53
+ node.value.expression.properties.length === 1
54
+ ) {
55
+ const htmlProp = node.value.expression.properties[0];
56
+ if (
57
+ htmlProp.type === "Property" &&
58
+ htmlProp.key.type === "Identifier" &&
59
+ htmlProp.key.name === "__html"
60
+ ) {
61
+ context.report({
62
+ node,
63
+ messageId: "dangerouslySetInnerHTML",
64
+ fix: (fixer) => {
65
+ const propRange = node.range;
66
+ const valueRange = htmlProp.value.range;
67
+ return [
68
+ fixer.replaceTextRange(
69
+ [propRange[0], valueRange[0]],
70
+ "innerHTML={",
71
+ ),
72
+ fixer.replaceTextRange(
73
+ [valueRange[1], propRange[1]],
74
+ "}",
75
+ ),
76
+ ];
77
+ },
78
+ });
79
+ } else {
80
+ context.report({
81
+ node,
82
+ messageId: "dangerouslySetInnerHTML",
83
+ });
84
+ }
85
+ } else {
86
+ context.report({
87
+ node,
88
+ messageId: "dangerouslySetInnerHTML",
89
+ });
90
+ }
91
+ return;
92
+ } else if (jsxPropName(node) !== "innerHTML") {
93
+ return;
94
+ }
95
+ if (allowStatic) {
96
+ const innerHtmlNode =
97
+ node.value?.type === "JSXExpressionContainer"
98
+ ? node.value.expression
99
+ : node.value;
100
+ const innerHtml =
101
+ innerHtmlNode && getStringIfConstant(innerHtmlNode);
102
+ if (typeof innerHtml === "string") {
103
+ if (isHtml(innerHtml)) {
104
+ // go up to enclosing JSXElement and check if it has children
105
+ if (
106
+ node.parent?.parent?.type === "JSXElement" &&
107
+ node.parent.parent.children?.length
108
+ ) {
109
+ context.report({
110
+ node: node.parent.parent, // report error on JSXElement instead of JSXAttribute
111
+ messageId: "conflict",
112
+ });
113
+ }
114
+ } else {
115
+ context.report({
116
+ node,
117
+ messageId: "notHtml",
118
+ suggest: [
119
+ {
120
+ fix: (fixer) =>
121
+ fixer.replaceText(
122
+ node.name,
123
+ "innerText",
124
+ ),
125
+ messageId: "useInnerText",
126
+ },
127
+ ],
128
+ });
129
+ }
130
+ } else {
131
+ context.report({
132
+ node,
133
+ messageId: "dangerous",
134
+ });
135
+ }
136
+ } else {
137
+ context.report({
138
+ node,
139
+ messageId: "dangerous",
140
+ });
141
+ }
142
+ },
143
+ };
144
+ },
145
+ });