@clue-ai/cli 0.0.8 → 0.0.10

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/src/contracts.mjs CHANGED
@@ -2,163 +2,168 @@ import { createRequire } from "node:module";
2
2
 
3
3
  const require = createRequire(import.meta.url);
4
4
  const schemaPackage = require("./public-schema.cjs");
5
+
5
6
  const {
6
- clueInitToolRequestSchema,
7
- clueInitToolReportSchema,
8
- semanticSnapshotRequestSchema,
9
- semanticSnapshotResponseSchema,
7
+ clueInitToolRequestSchema,
8
+ clueInitToolReportSchema,
9
+ semanticSnapshotRequestSchema,
10
+ semanticSnapshotResponseSchema,
10
11
  } = schemaPackage;
11
12
 
12
13
  const formatSchemaError = (result, field) => {
13
- const issues = result.error.issues.map((issue) => {
14
- const path = issue.path.length ? issue.path.join(".") : field;
15
- return `${path}: ${issue.message}`;
16
- });
17
- return `${field} is invalid: ${issues.join("; ")}`;
14
+ const issues = result.error.issues.map((issue) => {
15
+ const path = issue.path.length ? issue.path.join(".") : field;
16
+ return `${path}: ${issue.message}`;
17
+ });
18
+ return `${field} is invalid: ${issues.join("; ")}`;
18
19
  };
19
20
 
20
21
  const parseWithSchema = (schema, input, field) => {
21
- const result = schema.safeParse(input);
22
- if (!result.success) {
23
- throw new Error(formatSchemaError(result, field));
24
- }
25
- return result.data;
22
+ const result = schema.safeParse(input);
23
+ if (!result.success) {
24
+ throw new Error(formatSchemaError(result, field));
25
+ }
26
+ return result.data;
26
27
  };
27
28
 
28
29
  const nonEmpty = (value, field) => {
29
- if (typeof value !== "string" || value.trim() === "") {
30
- throw new Error(`${field} is required`);
31
- }
32
- return value.trim();
30
+ if (typeof value !== "string" || value.trim() === "") {
31
+ throw new Error(`${field} is required`);
32
+ }
33
+ return value.trim();
33
34
  };
34
35
 
35
36
  const optionalNonEmpty = (value) =>
36
- typeof value === "string" && value.trim() ? value.trim() : null;
37
+ typeof value === "string" && value.trim() ? value.trim() : null;
37
38
 
38
39
  const stringArray = (value, field, { min = 0 } = {}) => {
39
- if (!Array.isArray(value)) {
40
- throw new Error(`${field} must be an array`);
41
- }
42
- const result = value.map((entry) => nonEmpty(entry, field));
43
- if (result.length < min) {
44
- throw new Error(`${field} must include at least ${min} item(s)`);
45
- }
46
- return [...new Set(result)];
40
+ if (!Array.isArray(value)) {
41
+ throw new Error(`${field} must be an array`);
42
+ }
43
+ const result = value.map((entry) => nonEmpty(entry, field));
44
+ if (result.length < min) {
45
+ throw new Error(`${field} must include at least ${min} item(s)`);
46
+ }
47
+ return [...new Set(result)];
47
48
  };
48
49
 
49
50
  export const validateInitRequest = (input) => {
50
- const parsed = parseWithSchema(
51
- clueInitToolRequestSchema,
52
- input,
53
- "init request",
54
- );
55
- return {
56
- ...parsed,
57
- allowed_source_paths: [...new Set(parsed.allowed_source_paths)],
58
- excluded_source_paths: [...new Set(parsed.excluded_source_paths ?? [])],
59
- };
51
+ const parsed = parseWithSchema(
52
+ clueInitToolRequestSchema,
53
+ input,
54
+ "init request",
55
+ );
56
+ return {
57
+ ...parsed,
58
+ allowed_source_paths: [...new Set(parsed.allowed_source_paths)],
59
+ excluded_source_paths: [...new Set(parsed.excluded_source_paths ?? [])],
60
+ };
60
61
  };
61
62
 
62
63
  export const buildInitReport = ({
63
- request,
64
- lifecycleInsertions,
65
- requiredVariables = [],
66
- warnings = [],
64
+ request,
65
+ lifecycleInsertions,
66
+ requiredVariables = [],
67
+ warnings = [],
67
68
  }) =>
68
- parseWithSchema(
69
- clueInitToolReportSchema,
70
- {
71
- target_tool: request.target_tool,
72
- ci_workflow_path: request.ci_workflow_path,
73
- ci_workflow_added: true,
74
- required_secrets: ["CLUE_API_KEY", "CLUE_AI_PROVIDER_API_KEY"],
75
- required_variables: requiredVariables,
76
- lifecycle_insertions: lifecycleInsertions,
77
- semantic_generation_timing: "after_merge_ci",
78
- semantic_preview_generated: false,
79
- client_repo_generated_files_committed: false,
80
- clue_track_inserted: false,
81
- clue_dom_tag_inserted: false,
82
- allowed_source_paths: request.allowed_source_paths,
83
- excluded_source_paths: request.excluded_source_paths,
84
- warnings,
85
- },
86
- "init report",
87
- );
69
+ parseWithSchema(
70
+ clueInitToolReportSchema,
71
+ {
72
+ target_tool: request.target_tool,
73
+ ci_workflow_path: request.ci_workflow_path,
74
+ ci_workflow_added: true,
75
+ required_secrets: ["CLUE_API_KEY", "CLUE_AI_PROVIDER_API_KEY"],
76
+ required_variables: requiredVariables,
77
+ lifecycle_insertions: lifecycleInsertions,
78
+ semantic_generation_timing: "after_merge_ci",
79
+ semantic_preview_generated: false,
80
+ client_repo_generated_files_committed: false,
81
+ frontend_api_key_exposed: false,
82
+ browser_token_endpoint_required: true,
83
+ browser_token_provider_required: true,
84
+ browser_token_endpoint_path: "/api/v1/ingest/browser-tokens",
85
+ clue_track_inserted: false,
86
+ clue_dom_tag_inserted: false,
87
+ allowed_source_paths: request.allowed_source_paths,
88
+ excluded_source_paths: request.excluded_source_paths,
89
+ warnings,
90
+ },
91
+ "init report",
92
+ );
88
93
 
89
94
  export const validateSemanticCiRequest = (input) => ({
90
- project_key: nonEmpty(input.project_key, "project_key"),
91
- environment: nonEmpty(input.environment, "environment"),
92
- service_key: nonEmpty(input.service_key, "service_key"),
93
- repository: {
94
- provider: nonEmpty(input.repository?.provider, "repository.provider"),
95
- repository_id: nonEmpty(
96
- input.repository?.repository_id,
97
- "repository.repository_id",
98
- ),
99
- ...(optionalNonEmpty(input.repository?.owner)
100
- ? { owner: optionalNonEmpty(input.repository.owner) }
101
- : {}),
102
- ...(optionalNonEmpty(input.repository?.name)
103
- ? { name: optionalNonEmpty(input.repository.name) }
104
- : {}),
105
- ...(optionalNonEmpty(input.repository?.default_branch)
106
- ? { default_branch: optionalNonEmpty(input.repository.default_branch) }
107
- : {}),
108
- merge_commit: nonEmpty(
109
- input.repository?.merge_commit,
110
- "repository.merge_commit",
111
- ),
112
- ...(optionalNonEmpty(input.repository?.merged_at)
113
- ? { merged_at: optionalNonEmpty(input.repository.merged_at) }
114
- : {}),
115
- ...(input.repository?.pull_request_number === undefined ||
116
- input.repository?.pull_request_number === null
117
- ? {}
118
- : { pull_request_number: input.repository.pull_request_number }),
119
- ...(optionalNonEmpty(input.repository?.workflow_run_id)
120
- ? { workflow_run_id: optionalNonEmpty(input.repository.workflow_run_id) }
121
- : {}),
122
- },
123
- service: {
124
- service_key: nonEmpty(
125
- input.service?.service_key ?? input.service_key,
126
- "service.service_key",
127
- ),
128
- root_path: input.service?.root_path ?? null,
129
- framework: input.service?.framework ?? "fastapi",
130
- language: input.service?.language ?? "python",
131
- },
132
- allowed_source_paths: stringArray(
133
- input.allowed_source_paths,
134
- "allowed_source_paths",
135
- { min: 1 },
136
- ),
137
- excluded_source_paths: stringArray(
138
- input.excluded_source_paths ?? [],
139
- "excluded_source_paths",
140
- ),
141
- clue_api_base_url: nonEmpty(input.clue_api_base_url, "clue_api_base_url"),
142
- ai_provider_base_url: null,
143
- ai_provider: optionalNonEmpty(input.ai_provider),
144
- ai_model: optionalNonEmpty(input.ai_model),
95
+ project_key: nonEmpty(input.project_key, "project_key"),
96
+ environment: nonEmpty(input.environment, "environment"),
97
+ service_key: nonEmpty(input.service_key, "service_key"),
98
+ repository: {
99
+ provider: nonEmpty(input.repository?.provider, "repository.provider"),
100
+ repository_id: nonEmpty(
101
+ input.repository?.repository_id,
102
+ "repository.repository_id",
103
+ ),
104
+ ...(optionalNonEmpty(input.repository?.owner)
105
+ ? { owner: optionalNonEmpty(input.repository.owner) }
106
+ : {}),
107
+ ...(optionalNonEmpty(input.repository?.name)
108
+ ? { name: optionalNonEmpty(input.repository.name) }
109
+ : {}),
110
+ ...(optionalNonEmpty(input.repository?.default_branch)
111
+ ? { default_branch: optionalNonEmpty(input.repository.default_branch) }
112
+ : {}),
113
+ merge_commit: nonEmpty(
114
+ input.repository?.merge_commit,
115
+ "repository.merge_commit",
116
+ ),
117
+ ...(optionalNonEmpty(input.repository?.merged_at)
118
+ ? { merged_at: optionalNonEmpty(input.repository.merged_at) }
119
+ : {}),
120
+ ...(input.repository?.pull_request_number === undefined ||
121
+ input.repository?.pull_request_number === null
122
+ ? {}
123
+ : { pull_request_number: input.repository.pull_request_number }),
124
+ ...(optionalNonEmpty(input.repository?.workflow_run_id)
125
+ ? { workflow_run_id: optionalNonEmpty(input.repository.workflow_run_id) }
126
+ : {}),
127
+ },
128
+ service: {
129
+ service_key: nonEmpty(
130
+ input.service?.service_key ?? input.service_key,
131
+ "service.service_key",
132
+ ),
133
+ root_path: input.service?.root_path ?? null,
134
+ framework: input.service?.framework ?? "fastapi",
135
+ language: input.service?.language ?? "python",
136
+ },
137
+ allowed_source_paths: stringArray(
138
+ input.allowed_source_paths,
139
+ "allowed_source_paths",
140
+ { min: 1 },
141
+ ),
142
+ excluded_source_paths: stringArray(
143
+ input.excluded_source_paths ?? [],
144
+ "excluded_source_paths",
145
+ ),
146
+ clue_api_base_url: nonEmpty(input.clue_api_base_url, "clue_api_base_url"),
147
+ ai_provider_base_url: null,
148
+ ai_provider: optionalNonEmpty(input.ai_provider),
149
+ ai_model: optionalNonEmpty(input.ai_model),
145
150
  });
146
151
 
147
152
  export const buildOperationSourceKey = (method, pathTemplate) =>
148
- `route.${method.toUpperCase()}.${pathTemplate}`;
153
+ `route.${method.toUpperCase()}.${pathTemplate}`;
149
154
 
150
155
  export const validateSemanticSnapshotRequest = (input) => {
151
- return parseWithSchema(
152
- semanticSnapshotRequestSchema,
153
- input,
154
- "semantic snapshot",
155
- );
156
+ return parseWithSchema(
157
+ semanticSnapshotRequestSchema,
158
+ input,
159
+ "semantic snapshot",
160
+ );
156
161
  };
157
162
 
158
163
  export const validateSemanticSnapshotResponse = (input) => {
159
- return parseWithSchema(
160
- semanticSnapshotResponseSchema,
161
- input,
162
- "semantic snapshot response",
163
- );
164
+ return parseWithSchema(
165
+ semanticSnapshotResponseSchema,
166
+ input,
167
+ "semantic snapshot response",
168
+ );
164
169
  };
package/src/init-tool.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { buildInitReport, validateInitRequest } from "./contracts.mjs";
4
+ import { SEMANTIC_GEN_WORKFLOW_COMMAND } from "./cli-invocation.mjs";
4
5
  import {
5
6
  applyLifecyclePlan,
6
7
  planLifecycleInsertions,
@@ -158,7 +159,7 @@ jobs:
158
159
  CLUE_SEMANTIC_REQUEST_JSON: |
159
160
  ${indentMultiline(workflowRequestPayload(request), 12)}
160
161
  run: |
161
- npx @clue-ai/cli semantic-gen --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .
162
+ ${SEMANTIC_GEN_WORKFLOW_COMMAND}
162
163
  `;
163
164
 
164
165
  export const writeSemanticWorkflow = async ({ repoRoot, request }) => {
@@ -1,134 +1,197 @@
1
1
  const LIFECYCLE_CALL_PATTERN =
2
- /\b(ClueInit|ClueIdentify|ClueSetAccount|ClueLogout)\s*\(/g;
3
- const SAFE_HELPER_PATTERN =
4
- /\b(?:safeClue|safe_clue|safe_clue_call|safeClueCall|withClueGuard|with_clue_guard)\s*\(/g;
2
+ /\b(ClueInit|ClueIdentify|ClueSetAccount|ClueLogout|clue_init_fastapi|clue_init_django)\s*\(/g;
5
3
 
6
- const findMatchingDelimiter = (text, openIndex, open, close) => {
7
- let depth = 0;
8
- for (let index = openIndex; index < text.length; index += 1) {
9
- const character = text[index];
10
- if (character === open) depth += 1;
11
- if (character === close) {
12
- depth -= 1;
13
- if (depth === 0) return index;
4
+ export const stripSourceNoise = (text, { stripStrings = false } = {}) => {
5
+ let output = "";
6
+ let index = 0;
7
+ while (index < text.length) {
8
+ const char = text[index];
9
+ const next = text[index + 1];
10
+ if (char === "/" && next === "/") {
11
+ while (index < text.length && text[index] !== "\n") index += 1;
12
+ continue;
13
+ }
14
+ if (char === "#") {
15
+ while (index < text.length && text[index] !== "\n") index += 1;
16
+ continue;
14
17
  }
18
+ if (char === "/" && next === "*") {
19
+ index += 2;
20
+ while (
21
+ index < text.length &&
22
+ !(text[index] === "*" && text[index + 1] === "/")
23
+ ) {
24
+ if (text[index] === "\n") output += "\n";
25
+ index += 1;
26
+ }
27
+ index += index < text.length ? 2 : 0;
28
+ continue;
29
+ }
30
+ if (char === "'" || char === '"' || char === "`") {
31
+ const quote = char;
32
+ const triple =
33
+ quote !== "`" && text.slice(index, index + 3) === quote.repeat(3);
34
+ const endToken = triple ? quote.repeat(3) : quote;
35
+ if (!stripStrings) {
36
+ output += triple ? endToken : quote;
37
+ } else {
38
+ output += quote === "`" ? "``" : `${quote}${quote}`;
39
+ }
40
+ index += triple ? 3 : 1;
41
+ while (index < text.length) {
42
+ if (!triple && text[index] === "\\") {
43
+ if (!stripStrings) output += text.slice(index, index + 2);
44
+ index += 2;
45
+ continue;
46
+ }
47
+ if (text.slice(index, index + endToken.length) === endToken) {
48
+ if (!stripStrings) output += endToken;
49
+ index += endToken.length;
50
+ break;
51
+ }
52
+ if (text[index] === "\n") output += "\n";
53
+ if (!stripStrings && text[index] !== "\n") output += text[index];
54
+ index += 1;
55
+ }
56
+ continue;
57
+ }
58
+ output += char;
59
+ index += 1;
15
60
  }
16
- return -1;
61
+ return output;
17
62
  };
18
63
 
19
- const lineNumberForIndex = (text, index) =>
20
- text.slice(0, index).split("\n").length;
64
+ const isIdentifierCharacter = (character) =>
65
+ typeof character === "string" && /[A-Za-z0-9_$]/.test(character);
21
66
 
22
- const isAwaitedOnCallLine = (text, callIndex) => {
23
- const lineStart = text.lastIndexOf("\n", callIndex - 1) + 1;
24
- return /\bawait\b/.test(text.slice(lineStart, callIndex));
25
- };
67
+ const startsKeyword = (text, index, keyword) =>
68
+ text.startsWith(keyword, index) &&
69
+ !isIdentifierCharacter(text[index - 1]) &&
70
+ !isIdentifierCharacter(text[index + keyword.length]);
26
71
 
27
- const isInsideSafeHelperCall = (text, callIndex) => {
28
- for (const match of text.matchAll(SAFE_HELPER_PATTERN)) {
29
- const helperIndex = match.index ?? 0;
30
- if (helperIndex > callIndex) return false;
31
- const openParenIndex = text.indexOf("(", helperIndex);
32
- const closeParenIndex = findMatchingDelimiter(
33
- text,
34
- openParenIndex,
35
- "(",
36
- ")",
37
- );
38
- if (openParenIndex < callIndex && callIndex < closeParenIndex) return true;
72
+ const advanceQuotedString = (text, startIndex, { preserve = false } = {}) => {
73
+ const quote = text[startIndex];
74
+ const triple = quote !== "`" && text.slice(startIndex, startIndex + 3) === quote.repeat(3);
75
+ const endToken = triple ? quote.repeat(3) : quote;
76
+ let index = startIndex + (triple ? 3 : 1);
77
+ let value = preserve ? (triple ? endToken : quote) : "";
78
+ while (index < text.length) {
79
+ if (!triple && text[index] === "\\") {
80
+ if (preserve) value += text.slice(index, index + 2);
81
+ index += 2;
82
+ continue;
83
+ }
84
+ if (text.slice(index, index + endToken.length) === endToken) {
85
+ if (preserve) value += endToken;
86
+ index += endToken.length;
87
+ break;
88
+ }
89
+ if (preserve) value += text[index];
90
+ index += 1;
39
91
  }
40
- return false;
92
+ return { index, value };
41
93
  };
42
94
 
43
- const isInsideJsTryBlock = (text, callIndex) => {
44
- for (const match of text.matchAll(/\btry\s*{/g)) {
45
- const tryIndex = match.index ?? 0;
46
- if (tryIndex > callIndex) return false;
47
- const openBraceIndex = text.indexOf("{", tryIndex);
48
- const closeBraceIndex = findMatchingDelimiter(
49
- text,
50
- openBraceIndex,
51
- "{",
52
- "}",
53
- );
54
- if (openBraceIndex < callIndex && callIndex < closeBraceIndex) return true;
95
+ export const extractExecutableModuleStatements = (text) => {
96
+ const statements = [];
97
+ let index = 0;
98
+ while (index < text.length) {
99
+ const char = text[index];
100
+ const next = text[index + 1];
101
+ if (char === "/" && next === "/") {
102
+ while (index < text.length && text[index] !== "\n") index += 1;
103
+ continue;
104
+ }
105
+ if (char === "#") {
106
+ while (index < text.length && text[index] !== "\n") index += 1;
107
+ continue;
108
+ }
109
+ if (char === "/" && next === "*") {
110
+ index += 2;
111
+ while (
112
+ index < text.length &&
113
+ !(text[index] === "*" && text[index + 1] === "/")
114
+ ) {
115
+ index += 1;
116
+ }
117
+ index += index < text.length ? 2 : 0;
118
+ continue;
119
+ }
120
+ if (char === "'" || char === '"' || char === "`") {
121
+ index = advanceQuotedString(text, index).index;
122
+ continue;
123
+ }
124
+ if (!startsKeyword(text, index, "import") && !startsKeyword(text, index, "export")) {
125
+ index += 1;
126
+ continue;
127
+ }
128
+ let statement = "";
129
+ let depth = 0;
130
+ while (index < text.length) {
131
+ const current = text[index];
132
+ const following = text[index + 1];
133
+ if (current === "/" && following === "/") {
134
+ while (index < text.length && text[index] !== "\n") index += 1;
135
+ continue;
136
+ }
137
+ if (current === "/" && following === "*") {
138
+ index += 2;
139
+ while (
140
+ index < text.length &&
141
+ !(text[index] === "*" && text[index + 1] === "/")
142
+ ) {
143
+ index += 1;
144
+ }
145
+ index += index < text.length ? 2 : 0;
146
+ continue;
147
+ }
148
+ if (current === "'" || current === '"' || current === "`") {
149
+ const quoted = advanceQuotedString(text, index, { preserve: true });
150
+ statement += quoted.value;
151
+ index = quoted.index;
152
+ continue;
153
+ }
154
+ statement += current;
155
+ if (current === "{" || current === "(" || current === "[") depth += 1;
156
+ if (current === "}" || current === ")" || current === "]") {
157
+ depth = Math.max(0, depth - 1);
158
+ }
159
+ index += 1;
160
+ if (current === ";" && depth === 0) break;
161
+ if (current === "\n" && depth === 0) break;
162
+ }
163
+ const trimmed = statement.trim();
164
+ if (trimmed) statements.push(trimmed);
55
165
  }
56
- return false;
166
+ return statements;
57
167
  };
58
168
 
59
- const buildLines = (text) => {
60
- const lines = [];
61
- let start = 0;
62
- for (const line of text.split("\n")) {
63
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
64
- lines.push({
65
- start,
66
- end: start + line.length,
67
- indent,
68
- text: line,
69
- });
70
- start += line.length + 1;
71
- }
72
- return lines;
73
- };
169
+ const lineNumberForIndex = (text, index) =>
170
+ text.slice(0, index).split("\n").length;
74
171
 
75
- const isInsidePythonTryBlock = (text, callIndex) => {
76
- const lines = buildLines(text);
77
- const callLineIndex = lines.findIndex(
78
- (line) => line.start <= callIndex && callIndex <= line.end,
79
- );
80
- if (callLineIndex < 0) return false;
81
- const callLine = lines[callLineIndex];
82
- for (let index = callLineIndex - 1; index >= 0; index -= 1) {
83
- const candidate = lines[index];
84
- if (!candidate.text.trim()) continue;
85
- if (!/^\s*try\s*:\s*(?:#.*)?$/.test(candidate.text)) continue;
86
- if (candidate.indent >= callLine.indent) continue;
87
- const escaped = lines
88
- .slice(index + 1, callLineIndex)
89
- .some(
90
- (line) =>
91
- line.text.trim() &&
92
- line.indent <= candidate.indent &&
93
- !/^\s*(?:except|finally|else)\b/.test(line.text),
94
- );
95
- if (!escaped) return true;
96
- }
97
- return false;
172
+ const isAwaitedOnCallLine = (text, callIndex) => {
173
+ const lineStart = text.lastIndexOf("\n", callIndex - 1) + 1;
174
+ return /\bawait\b/.test(text.slice(lineStart, callIndex));
98
175
  };
99
176
 
100
- const hasCatchHandlerOnCall = (text, callIndex) => {
101
- const openParenIndex = text.indexOf("(", callIndex);
102
- const closeParenIndex = findMatchingDelimiter(text, openParenIndex, "(", ")");
103
- if (closeParenIndex < 0) return false;
104
- return /^\s*\.catch\s*\(/.test(text.slice(closeParenIndex + 1));
177
+ const canonicalLifecycleApiName = (apiName) => {
178
+ if (apiName === "clue_init_fastapi" || apiName === "clue_init_django") {
179
+ return "ClueInit";
180
+ }
181
+ return apiName;
105
182
  };
106
183
 
107
- const isGuardedLifecycleCall = (text, callIndex) =>
108
- hasCatchHandlerOnCall(text, callIndex) ||
109
- isInsideSafeHelperCall(text, callIndex) ||
110
- isInsideJsTryBlock(text, callIndex) ||
111
- isInsidePythonTryBlock(text, callIndex);
112
-
113
184
  export const findLifecycleGuardViolations = (text) => {
114
185
  const violations = [];
115
186
  for (const match of text.matchAll(LIFECYCLE_CALL_PATTERN)) {
116
187
  const callIndex = match.index ?? 0;
117
- const apiName = match[1];
188
+ const apiName = canonicalLifecycleApiName(match[1]);
118
189
  if (isAwaitedOnCallLine(text, callIndex)) {
119
190
  violations.push({
120
191
  api_name: apiName,
121
192
  line: lineNumberForIndex(text, callIndex),
122
193
  reason: "awaited_lifecycle_call",
123
194
  });
124
- continue;
125
- }
126
- if (!isGuardedLifecycleCall(text, callIndex)) {
127
- violations.push({
128
- api_name: apiName,
129
- line: lineNumberForIndex(text, callIndex),
130
- reason: "unguarded_lifecycle_call",
131
- });
132
195
  }
133
196
  }
134
197
  return violations;
@@ -136,6 +199,8 @@ export const findLifecycleGuardViolations = (text) => {
136
199
 
137
200
  export const findLifecycleCallApiNames = (text) => [
138
201
  ...new Set(
139
- [...text.matchAll(LIFECYCLE_CALL_PATTERN)].map((match) => match[1]),
202
+ [...text.matchAll(LIFECYCLE_CALL_PATTERN)].map((match) =>
203
+ canonicalLifecycleApiName(match[1]),
204
+ ),
140
205
  ),
141
206
  ];