@fitzzero/quickdraw-core 3.6.0 → 3.8.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 (37) hide show
  1. package/README.md +218 -64
  2. package/dist/client/index.d.ts +34 -4
  3. package/dist/client/index.js +57 -65
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/client/testing.d.ts +1 -1
  6. package/dist/client/testing.js.map +1 -1
  7. package/dist/server/express/index.d.ts +37 -0
  8. package/dist/server/express/index.js +60 -0
  9. package/dist/server/express/index.js.map +1 -0
  10. package/dist/server/index.d.ts +330 -5
  11. package/dist/server/index.js +593 -144
  12. package/dist/server/index.js.map +1 -1
  13. package/dist/server/testing-prisma.d.ts +101 -0
  14. package/dist/server/testing-prisma.js +172 -0
  15. package/dist/server/testing-prisma.js.map +1 -0
  16. package/dist/server/testing.d.ts +1 -1
  17. package/dist/server/testing.js +87 -22
  18. package/dist/server/testing.js.map +1 -1
  19. package/dist/shared/index.d.ts +77 -1
  20. package/dist/shared/index.js +5 -1
  21. package/dist/shared/index.js.map +1 -1
  22. package/dist/{types-DniMxkQs.d.ts → types-ClgSr90l.d.ts} +5 -1
  23. package/dist/{types-B1z5aXdR.d.ts → types-DUHPIy_5.d.ts} +20 -1
  24. package/eslint-config/index.mjs +1 -2
  25. package/eslint-plugin-quickdraw/index.mjs +15 -7
  26. package/eslint-plugin-quickdraw/rules/no-cross-service-mutations.mjs +31 -32
  27. package/eslint-plugin-quickdraw/rules/no-direct-prisma-mutations.mjs +17 -18
  28. package/eslint-plugin-quickdraw/rules/no-raw-button-strings.mjs +52 -0
  29. package/eslint-plugin-quickdraw/rules/no-raw-service-room-string.mjs +140 -0
  30. package/eslint-plugin-quickdraw/rules/no-raw-socket-emit.mjs +20 -22
  31. package/eslint-plugin-quickdraw/rules/no-raw-socket-on.mjs +17 -17
  32. package/eslint-plugin-quickdraw/rules/no-raw-tooltip-strings.mjs +59 -0
  33. package/eslint-plugin-quickdraw/rules/no-raw-typography-strings.mjs +52 -0
  34. package/eslint-plugin-quickdraw/rules/no-service-method-record.mjs +3 -3
  35. package/eslint-plugin-quickdraw/rules/no-unsafe-payload-cast.mjs +8 -8
  36. package/eslint-plugin-quickdraw/rules/require-zod-schema.mjs +15 -20
  37. package/package.json +74 -50
@@ -1,4 +1,4 @@
1
- const MUTATION_METHODS = new Set(["create", "update", "delete"])
1
+ const MUTATION_METHODS = new Set(["create", "update", "delete"]);
2
2
 
3
3
  /**
4
4
  * Extract the service directory name from a file path.
@@ -6,8 +6,8 @@ const MUTATION_METHODS = new Set(["create", "update", "delete"])
6
6
  * "/app/services/task/helpers.ts" → "task"
7
7
  */
8
8
  function getServiceDirName(filename) {
9
- const match = filename.match(/services\/([^/]+)\//)
10
- return match ? match[1] : null
9
+ const match = filename.match(/services\/([^/]+)\//);
10
+ return match ? match[1] : null;
11
11
  }
12
12
 
13
13
  /**
@@ -16,7 +16,7 @@ function getServiceDirName(filename) {
16
16
  * e.g. "cloud-build" → "cloudBuild", "task" → "task"
17
17
  */
18
18
  function dirNameToModelName(dirName) {
19
- return dirName.replace(/[-_](\w)/g, (_, c) => c.toUpperCase())
19
+ return dirName.replace(/[-_](\w)/g, (_, c) => c.toUpperCase());
20
20
  }
21
21
 
22
22
  /** @type {import('eslint').Rule.RuleModule} */
@@ -56,38 +56,37 @@ export default {
56
56
  ],
57
57
  },
58
58
  create(context) {
59
- const filename = context.filename || context.getFilename()
60
- const serviceDirName = getServiceDirName(filename)
59
+ const filename = context.filename || context.getFilename();
60
+ const serviceDirName = getServiceDirName(filename);
61
61
 
62
- if (!serviceDirName) return {}
62
+ if (!serviceDirName) return {};
63
63
 
64
- const ownedModel = dirNameToModelName(serviceDirName)
65
- const isIndexFile = filename.endsWith("/index.ts") || filename.endsWith("/index.js")
66
- const options = context.options[0] || {}
67
- const allowedModels = options.allowedModels || {}
68
- const allowedForThisService = new Set(allowedModels[serviceDirName] || [])
64
+ const ownedModel = dirNameToModelName(serviceDirName);
65
+ const isIndexFile = filename.endsWith("/index.ts") || filename.endsWith("/index.js");
66
+ const options = context.options[0] || {};
67
+ const allowedModels = options.allowedModels || {};
68
+ const allowedForThisService = new Set(allowedModels[serviceDirName] || []);
69
69
 
70
70
  return {
71
71
  CallExpression(node) {
72
- const callee = node.callee
73
- if (callee.type !== "MemberExpression") return
74
- if (callee.property.type !== "Identifier") return
72
+ const callee = node.callee;
73
+ if (callee.type !== "MemberExpression") return;
74
+ if (callee.property.type !== "Identifier") return;
75
75
 
76
- const methodName = callee.property.name
77
- if (!MUTATION_METHODS.has(methodName)) return
76
+ const methodName = callee.property.name;
77
+ if (!MUTATION_METHODS.has(methodName)) return;
78
78
 
79
- const obj = callee.object
80
- if (obj.type !== "MemberExpression") return
79
+ const obj = callee.object;
80
+ if (obj.type !== "MemberExpression") return;
81
81
 
82
- const parentObj = obj.object
83
- if (parentObj.type !== "MemberExpression") return
84
- if (parentObj.property.type !== "Identifier") return
85
- if (parentObj.property.name !== "prisma") return
82
+ const parentObj = obj.object;
83
+ if (parentObj.type !== "MemberExpression") return;
84
+ if (parentObj.property.type !== "Identifier") return;
85
+ if (parentObj.property.name !== "prisma") return;
86
86
 
87
- if (parentObj.object.type !== "ThisExpression") return
87
+ if (parentObj.object.type !== "ThisExpression") return;
88
88
 
89
- const modelName =
90
- obj.property.type === "Identifier" ? obj.property.name : "model"
89
+ const modelName = obj.property.type === "Identifier" ? obj.property.name : "model";
91
90
 
92
91
  if (modelName === ownedModel) {
93
92
  if (!isIndexFile) {
@@ -95,19 +94,19 @@ export default {
95
94
  node,
96
95
  messageId: "useBaseService",
97
96
  data: { method: methodName, model: modelName },
98
- })
97
+ });
99
98
  }
100
- return
99
+ return;
101
100
  }
102
101
 
103
- if (allowedForThisService.has(modelName)) return
102
+ if (allowedForThisService.has(modelName)) return;
104
103
 
105
104
  context.report({
106
105
  node,
107
106
  messageId: "crossServiceMutation",
108
107
  data: { method: methodName, model: modelName },
109
- })
108
+ });
110
109
  },
111
- }
110
+ };
112
111
  },
113
- }
112
+ };
@@ -14,36 +14,35 @@ export default {
14
14
  schema: [],
15
15
  },
16
16
  create(context) {
17
- const MUTATION_METHODS = new Set(["create", "update", "delete"])
17
+ const MUTATION_METHODS = new Set(["create", "update", "delete"]);
18
18
 
19
19
  return {
20
20
  CallExpression(node) {
21
- const callee = node.callee
22
- if (callee.type !== "MemberExpression") return
23
- if (callee.property.type !== "Identifier") return
21
+ const callee = node.callee;
22
+ if (callee.type !== "MemberExpression") return;
23
+ if (callee.property.type !== "Identifier") return;
24
24
 
25
- const methodName = callee.property.name
26
- if (!MUTATION_METHODS.has(methodName)) return
25
+ const methodName = callee.property.name;
26
+ if (!MUTATION_METHODS.has(methodName)) return;
27
27
 
28
- const obj = callee.object
29
- if (obj.type !== "MemberExpression") return
28
+ const obj = callee.object;
29
+ if (obj.type !== "MemberExpression") return;
30
30
 
31
- const parentObj = obj.object
32
- if (parentObj.type !== "MemberExpression") return
33
- if (parentObj.property.type !== "Identifier") return
34
- if (parentObj.property.name !== "prisma") return
31
+ const parentObj = obj.object;
32
+ if (parentObj.type !== "MemberExpression") return;
33
+ if (parentObj.property.type !== "Identifier") return;
34
+ if (parentObj.property.name !== "prisma") return;
35
35
 
36
- if (parentObj.object.type !== "ThisExpression") return
36
+ if (parentObj.object.type !== "ThisExpression") return;
37
37
 
38
- const modelName =
39
- obj.property.type === "Identifier" ? obj.property.name : "model"
38
+ const modelName = obj.property.type === "Identifier" ? obj.property.name : "model";
40
39
 
41
40
  context.report({
42
41
  node,
43
42
  messageId: "noDirectMutation",
44
43
  data: { method: methodName, model: modelName },
45
- })
44
+ });
46
45
  },
47
- }
46
+ };
48
47
  },
49
- }
48
+ };
@@ -0,0 +1,52 @@
1
+ /** @type {import('eslint').Rule.RuleModule} */
2
+ export default {
3
+ meta: {
4
+ type: "problem",
5
+ docs: {
6
+ description:
7
+ "Disallow raw string literals in Button components. Use useTranslations() or the <T> component for localization.",
8
+ },
9
+ messages: {
10
+ noRawButtonStrings:
11
+ "Raw string '{{content}}' in Button component should be localized. Use t('key') or <T>key</T> instead.",
12
+ },
13
+ schema: [],
14
+ },
15
+ create(context) {
16
+ return {
17
+ JSXElement(node) {
18
+ // Check if this is a Button element
19
+ if (
20
+ node.openingElement.name.type !== "JSXIdentifier" ||
21
+ node.openingElement.name.name !== "Button"
22
+ ) {
23
+ return;
24
+ }
25
+
26
+ // Check all children for raw strings
27
+ for (const child of node.children) {
28
+ if (child.type === "JSXText") {
29
+ // Flag JSXText that contains non-whitespace characters
30
+ const trimmedText = child.value.trim();
31
+ if (trimmedText.length > 0) {
32
+ context.report({
33
+ node: child,
34
+ messageId: "noRawButtonStrings",
35
+ data: { content: trimmedText },
36
+ });
37
+ }
38
+ } else if (child.type === "JSXExpressionContainer") {
39
+ // Flag JSXExpressionContainer where the expression is a string literal
40
+ if (child.expression.type === "Literal" && typeof child.expression.value === "string") {
41
+ context.report({
42
+ node: child,
43
+ messageId: "noRawButtonStrings",
44
+ data: { content: child.expression.value },
45
+ });
46
+ }
47
+ }
48
+ }
49
+ },
50
+ };
51
+ },
52
+ };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Bans raw template literals used as room name strings in emit/to/join calls.
3
+ * Use serviceRoom(), userRoom(), etc. helpers from your shared package instead.
4
+ *
5
+ * Options: { additionalPatterns: string[] } — app-specific room patterns
6
+ * (regex sources) checked in addition to the defaults.
7
+ */
8
+
9
+ const DEFAULT_ROOM_PATTERNS = [/Service:\$\{/, /^user:\$\{/];
10
+
11
+ /**
12
+ * Check if a template literal node looks like a room string.
13
+ */
14
+ function isRoomTemplateLiteral(node, patterns) {
15
+ if (node.type !== "TemplateLiteral" || node.expressions.length === 0) {
16
+ return false;
17
+ }
18
+
19
+ // Reconstruct the raw template pattern by replacing expressions with ${...}
20
+ let raw = "";
21
+ for (let i = 0; i < node.quasis.length; i++) {
22
+ raw += node.quasis[i].value.raw;
23
+ if (i < node.expressions.length) {
24
+ raw += "${...}";
25
+ }
26
+ }
27
+
28
+ return patterns.some((pattern) => pattern.test(raw));
29
+ }
30
+
31
+ /**
32
+ * Check if a node is a call to emitToRoom, .to(), .join(), or
33
+ * emitConnectedUsers and return the argument index that represents the room name.
34
+ */
35
+ function getRoomArgIndex(node) {
36
+ if (node.type !== "CallExpression") return -1;
37
+
38
+ const callee = node.callee;
39
+
40
+ // this.emitToRoom(ROOM, ...) or service.emitToRoom(ROOM, ...)
41
+ if (
42
+ callee.type === "MemberExpression" &&
43
+ callee.property.type === "Identifier" &&
44
+ callee.property.name === "emitToRoom"
45
+ ) {
46
+ return 0;
47
+ }
48
+
49
+ // .to(ROOM) — Socket.IO chaining
50
+ if (
51
+ callee.type === "MemberExpression" &&
52
+ callee.property.type === "Identifier" &&
53
+ callee.property.name === "to"
54
+ ) {
55
+ return 0;
56
+ }
57
+
58
+ // .join(ROOM) — Socket.IO room joining
59
+ if (
60
+ callee.type === "MemberExpression" &&
61
+ callee.property.type === "Identifier" &&
62
+ callee.property.name === "join"
63
+ ) {
64
+ return 0;
65
+ }
66
+
67
+ // this.emitConnectedUsers(entityId, ROOM)
68
+ if (
69
+ callee.type === "MemberExpression" &&
70
+ callee.property.type === "Identifier" &&
71
+ callee.property.name === "emitConnectedUsers"
72
+ ) {
73
+ return 1;
74
+ }
75
+
76
+ return -1;
77
+ }
78
+
79
+ /** @type {import('eslint').Rule.RuleModule} */
80
+ export default {
81
+ meta: {
82
+ type: "problem",
83
+ docs: {
84
+ description:
85
+ "Disallow raw template literal room strings in emit/to/join calls. Use serviceRoom() or other room helpers from your shared package.",
86
+ },
87
+ messages: {
88
+ noRawRoomString:
89
+ "Use serviceRoom() or other room helpers from your shared package instead of raw template literals for room names.",
90
+ },
91
+ schema: [
92
+ {
93
+ type: "object",
94
+ properties: {
95
+ additionalPatterns: {
96
+ type: "array",
97
+ items: { type: "string" },
98
+ },
99
+ },
100
+ additionalProperties: false,
101
+ },
102
+ ],
103
+ },
104
+ create(context) {
105
+ const options = context.options[0] ?? {};
106
+ const patterns = [
107
+ ...DEFAULT_ROOM_PATTERNS,
108
+ ...(options.additionalPatterns ?? []).map((source) => new RegExp(source)),
109
+ ];
110
+
111
+ return {
112
+ CallExpression(node) {
113
+ const argIndex = getRoomArgIndex(node);
114
+ if (argIndex < 0 || !node.arguments[argIndex]) return;
115
+
116
+ const arg = node.arguments[argIndex];
117
+ if (isRoomTemplateLiteral(arg, patterns)) {
118
+ context.report({
119
+ node: arg,
120
+ messageId: "noRawRoomString",
121
+ });
122
+ }
123
+ },
124
+ // Also catch: const room = `chatService:${id}`;
125
+ VariableDeclarator(node) {
126
+ if (
127
+ node.id.type === "Identifier" &&
128
+ /room/i.test(node.id.name) &&
129
+ node.init &&
130
+ isRoomTemplateLiteral(node.init, patterns)
131
+ ) {
132
+ context.report({
133
+ node: node.init,
134
+ messageId: "noRawRoomString",
135
+ });
136
+ }
137
+ },
138
+ };
139
+ },
140
+ };
@@ -2,12 +2,12 @@
2
2
  * Allowed event name prefixes that are exempt from this rule.
3
3
  * These are documented exceptions for protocols that don't map to BaseService.
4
4
  */
5
- const DEFAULT_ALLOWED_PREFIXES = ["agentRunner:", "projectRunner:"]
5
+ const DEFAULT_ALLOWED_PREFIXES = ["agentRunner:", "projectRunner:"];
6
6
 
7
7
  /**
8
8
  * Allowed exact event names that are exempt from this rule.
9
9
  */
10
- const DEFAULT_ALLOWED_EVENTS = new Set(["subscribe", "unsubscribe"])
10
+ const DEFAULT_ALLOWED_EVENTS = new Set(["subscribe", "unsubscribe"]);
11
11
 
12
12
  /** @type {import('eslint').Rule.RuleModule} */
13
13
  export default {
@@ -51,45 +51,43 @@ export default {
51
51
  ],
52
52
  },
53
53
  create(context) {
54
- const options = context.options[0] || {}
55
- const allowedPrefixes = options.allowedPrefixes || DEFAULT_ALLOWED_PREFIXES
56
- const allowedEvents = new Set(
57
- options.allowedEvents || DEFAULT_ALLOWED_EVENTS
58
- )
54
+ const options = context.options[0] || {};
55
+ const allowedPrefixes = options.allowedPrefixes || DEFAULT_ALLOWED_PREFIXES;
56
+ const allowedEvents = new Set(options.allowedEvents || DEFAULT_ALLOWED_EVENTS);
59
57
 
60
58
  return {
61
59
  CallExpression(node) {
62
- const callee = node.callee
63
- if (callee.type !== "MemberExpression") return
64
- if (callee.property.type !== "Identifier") return
65
- if (callee.property.name !== "emit") return
60
+ const callee = node.callee;
61
+ if (callee.type !== "MemberExpression") return;
62
+ if (callee.property.type !== "Identifier") return;
63
+ if (callee.property.name !== "emit") return;
66
64
 
67
- const obj = callee.object
68
- if (obj.type !== "Identifier" || obj.name !== "socket") return
65
+ const obj = callee.object;
66
+ if (obj.type !== "Identifier" || obj.name !== "socket") return;
69
67
 
70
- const eventArg = node.arguments[0]
71
- if (!eventArg) return
68
+ const eventArg = node.arguments[0];
69
+ if (!eventArg) return;
72
70
 
73
71
  if (eventArg.type === "Literal" && typeof eventArg.value === "string") {
74
- const eventName = eventArg.value
72
+ const eventName = eventArg.value;
75
73
 
76
- if (allowedEvents.has(eventName)) return
74
+ if (allowedEvents.has(eventName)) return;
77
75
  if (allowedPrefixes.some((prefix) => eventName.startsWith(prefix))) {
78
- return
76
+ return;
79
77
  }
80
78
 
81
79
  context.report({
82
80
  node,
83
81
  messageId: "noRawSocketEmit",
84
82
  data: { eventName },
85
- })
83
+ });
86
84
  } else {
87
85
  context.report({
88
86
  node,
89
87
  messageId: "noRawSocketEmitDynamic",
90
- })
88
+ });
91
89
  }
92
90
  },
93
- }
91
+ };
94
92
  },
95
- }
93
+ };
@@ -2,7 +2,7 @@
2
2
  * Allowed event name prefixes that are exempt from this rule.
3
3
  * These are documented exceptions for protocols that don't map to BaseService.
4
4
  */
5
- const DEFAULT_ALLOWED_PREFIXES = ["agentRunner:", "projectRunner:"]
5
+ const DEFAULT_ALLOWED_PREFIXES = ["agentRunner:", "projectRunner:"];
6
6
 
7
7
  /** @type {import('eslint').Rule.RuleModule} */
8
8
  export default {
@@ -39,41 +39,41 @@ export default {
39
39
  ],
40
40
  },
41
41
  create(context) {
42
- const options = context.options[0] || {}
43
- const allowedPrefixes = options.allowedPrefixes || DEFAULT_ALLOWED_PREFIXES
42
+ const options = context.options[0] || {};
43
+ const allowedPrefixes = options.allowedPrefixes || DEFAULT_ALLOWED_PREFIXES;
44
44
 
45
45
  return {
46
46
  CallExpression(node) {
47
- const callee = node.callee
48
- if (callee.type !== "MemberExpression") return
49
- if (callee.property.type !== "Identifier") return
50
- if (callee.property.name !== "on") return
47
+ const callee = node.callee;
48
+ if (callee.type !== "MemberExpression") return;
49
+ if (callee.property.type !== "Identifier") return;
50
+ if (callee.property.name !== "on") return;
51
51
 
52
- const obj = callee.object
53
- if (obj.type !== "Identifier" || obj.name !== "socket") return
52
+ const obj = callee.object;
53
+ if (obj.type !== "Identifier" || obj.name !== "socket") return;
54
54
 
55
- const eventArg = node.arguments[0]
56
- if (!eventArg) return
55
+ const eventArg = node.arguments[0];
56
+ if (!eventArg) return;
57
57
 
58
58
  if (eventArg.type === "Literal" && typeof eventArg.value === "string") {
59
- const eventName = eventArg.value
59
+ const eventName = eventArg.value;
60
60
 
61
61
  if (allowedPrefixes.some((prefix) => eventName.startsWith(prefix))) {
62
- return
62
+ return;
63
63
  }
64
64
 
65
65
  context.report({
66
66
  node,
67
67
  messageId: "noRawSocketOn",
68
68
  data: { eventName },
69
- })
69
+ });
70
70
  } else {
71
71
  context.report({
72
72
  node,
73
73
  messageId: "noRawSocketOnDynamic",
74
- })
74
+ });
75
75
  }
76
76
  },
77
- }
77
+ };
78
78
  },
79
- }
79
+ };
@@ -0,0 +1,59 @@
1
+ /** @type {import('eslint').Rule.RuleModule} */
2
+ export default {
3
+ meta: {
4
+ type: "problem",
5
+ docs: {
6
+ description:
7
+ "Disallow raw string literals in Tooltip title props. Use useTranslations() for localization.",
8
+ },
9
+ messages: {
10
+ noRawTooltipStrings:
11
+ "Raw string '{{content}}' in Tooltip title should be localized. Use t('key') instead.",
12
+ },
13
+ schema: [],
14
+ },
15
+ create(context) {
16
+ return {
17
+ JSXOpeningElement(node) {
18
+ if (node.name.type !== "JSXIdentifier" || node.name.name !== "Tooltip") {
19
+ return;
20
+ }
21
+
22
+ const titleAttr = node.attributes.find(
23
+ (a) => a.type === "JSXAttribute" && a.name.name === "title",
24
+ );
25
+ if (!titleAttr) return;
26
+
27
+ // Check for title="raw string"
28
+ if (
29
+ titleAttr.value &&
30
+ (titleAttr.value.type === "StringLiteral" || titleAttr.value.type === "Literal") &&
31
+ typeof titleAttr.value.value === "string" &&
32
+ titleAttr.value.value.trim().length > 0
33
+ ) {
34
+ context.report({
35
+ node: titleAttr,
36
+ messageId: "noRawTooltipStrings",
37
+ data: { content: titleAttr.value.value },
38
+ });
39
+ }
40
+
41
+ // Check for title={"raw string"}
42
+ if (
43
+ titleAttr.value &&
44
+ titleAttr.value.type === "JSXExpressionContainer" &&
45
+ titleAttr.value.expression &&
46
+ titleAttr.value.expression.type === "Literal" &&
47
+ typeof titleAttr.value.expression.value === "string" &&
48
+ titleAttr.value.expression.value.trim().length > 0
49
+ ) {
50
+ context.report({
51
+ node: titleAttr,
52
+ messageId: "noRawTooltipStrings",
53
+ data: { content: titleAttr.value.expression.value },
54
+ });
55
+ }
56
+ },
57
+ };
58
+ },
59
+ };
@@ -0,0 +1,52 @@
1
+ /** @type {import('eslint').Rule.RuleModule} */
2
+ export default {
3
+ meta: {
4
+ type: "problem",
5
+ docs: {
6
+ description:
7
+ "Disallow raw string literals in Typography components. Use useTranslations() or the <T> component for localization.",
8
+ },
9
+ messages: {
10
+ noRawTypographyStrings:
11
+ "Raw string '{{content}}' in Typography component should be localized. Use t('key') or <T>key</T> instead.",
12
+ },
13
+ schema: [],
14
+ },
15
+ create(context) {
16
+ return {
17
+ JSXElement(node) {
18
+ // Check if this is a Typography element
19
+ if (
20
+ node.openingElement.name.type !== "JSXIdentifier" ||
21
+ node.openingElement.name.name !== "Typography"
22
+ ) {
23
+ return;
24
+ }
25
+
26
+ // Check all children for raw strings
27
+ for (const child of node.children) {
28
+ if (child.type === "JSXText") {
29
+ // Flag JSXText that contains non-whitespace characters
30
+ const trimmedText = child.value.trim();
31
+ if (trimmedText.length > 0) {
32
+ context.report({
33
+ node: child,
34
+ messageId: "noRawTypographyStrings",
35
+ data: { content: trimmedText },
36
+ });
37
+ }
38
+ } else if (child.type === "JSXExpressionContainer") {
39
+ // Flag JSXExpressionContainer where the expression is a string literal
40
+ if (child.expression.type === "Literal" && typeof child.expression.value === "string") {
41
+ context.report({
42
+ node: child,
43
+ messageId: "noRawTypographyStrings",
44
+ data: { content: child.expression.value },
45
+ });
46
+ }
47
+ }
48
+ }
49
+ },
50
+ };
51
+ },
52
+ };
@@ -26,10 +26,10 @@ export default {
26
26
  context.report({
27
27
  node: member,
28
28
  messageId: "noServiceMethodRecord",
29
- })
29
+ });
30
30
  }
31
31
  }
32
32
  },
33
- }
33
+ };
34
34
  },
35
- }
35
+ };
@@ -17,12 +17,12 @@ export default {
17
17
  create(context) {
18
18
  return {
19
19
  TSAsExpression(node) {
20
- const typeAnnotation = node.typeAnnotation
21
- if (typeAnnotation.type !== "TSTypeReference") return
22
- if (typeAnnotation.typeName?.type !== "TSQualifiedName") return
20
+ const typeAnnotation = node.typeAnnotation;
21
+ if (typeAnnotation.type !== "TSTypeReference") return;
22
+ if (typeAnnotation.typeName?.type !== "TSQualifiedName") return;
23
23
 
24
- const left = typeAnnotation.typeName.left
25
- const right = typeAnnotation.typeName.right
24
+ const left = typeAnnotation.typeName.left;
25
+ const right = typeAnnotation.typeName.right;
26
26
 
27
27
  if (
28
28
  left.type === "Identifier" &&
@@ -33,9 +33,9 @@ export default {
33
33
  context.report({
34
34
  node,
35
35
  messageId: "noUnsafePayloadCast",
36
- })
36
+ });
37
37
  }
38
38
  },
39
- }
39
+ };
40
40
  },
41
- }
41
+ };