Repository navigation
fix(GenesisKit): no @Entry macro, so the app builds without Xcode's SwiftUI macro plugin #568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
31017ee
77d2778
a7e4abb
0fa28de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -766,9 +766,20 @@ const SWIFT_CLICK_COUNT_RULE = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SWIFT_CURRENT_EVENT_CLICK_COUNT = /currentEvent\s*[?!]?\s*\.clickCount/; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SWIFT_ENTRY_MACRO_RULE = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "swift-no-entry-macro", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| severity: "error" as const, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "`@Entry` is a SwiftUI macro: its plugin (SwiftUIMacros) ships only with Xcode, so the app stops building on " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "a Mac with only the Command Line Tools (#564). Write a `private struct <Name>Key: EnvironmentKey` and a " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "computed `EnvironmentValues` property with `get`/`set`, as `Style/MotionEnvironment.swift` does.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SWIFT_ENTRY_MACRO = /@Entry\b/; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
genesiscz marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Quality | 🟡 Medium · confidence 87/100 Also reject module-qualified SwiftUI.Entry attributes Swift permits module-qualified macro attributes, so 🧩 AnalysisGrep evidence:
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valid. The rule should also match @SwiftUI.Entry, with a positive test for the qualified spelling. No file in the tree uses that spelling today (the whole-tree lint and a search find none), so nothing builds with the macro plugin now. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Quality | 🟡 Medium · confidence 85/100 🛠️ Refactor suggestion Regression: module-qualified @SwiftUI.Entry bypasses the guard again The previously reported module-qualified-attribute issue is present again in the current head: the new masking/line-rule implementation uses only 🧩 AnalysisGrep evidence:
Suggested change
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same finding as the earlier @SwiftUI.Entry thread on this line, which is deferred with a positive test for the qualified spelling. No file in the tree uses that spelling today. The pull request was merged after its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SWIFT_LINE_RULES = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { rule: SWIFT_WAIT_RULE, pattern: SWIFT_BARE_WAIT }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { rule: SWIFT_CLICK_COUNT_RULE, pattern: SWIFT_CURRENT_EVENT_CLICK_COUNT }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { rule: SWIFT_ENTRY_MACRO_RULE, pattern: SWIFT_ENTRY_MACRO }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1080,9 +1091,101 @@ function recordBusyWaitFindings(root: SgNode, record: RecordFinding): void { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recordSleepSyncFindings(root.findAll(SLEEP_SYNC_IN_LOOP_MATCHER), record); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface SwiftLintString { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hashes: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interpolationDepth: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Blank Swift comments, strings and hash-delimited regexes without moving code's line or column offsets. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function maskSwiftNonCode(source: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const masked = source.split(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let index = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let blockDepth = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let regexEnd = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const strings: SwiftLintString[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const blank = (end: number) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (index < end) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (source[index] !== "\n" && source[index] !== "\r") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| masked[index] = " "; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (index < source.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const string = strings.at(-1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (blockDepth > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (source.startsWith("/*", index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blockDepth++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (source.startsWith("*/", index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blockDepth--; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (regexEnd) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (source[index] === "\\") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(Math.min(index + 2, source.length)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (source.startsWith(regexEnd, index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + regexEnd.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| regexEnd = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (string?.interpolationDepth === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (source.startsWith(string.end, index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + string.end.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strings.pop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (source.startsWith(`\\${string.hashes}(`, index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + string.hashes.length + 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| string.interpolationDepth = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (source.startsWith(`\\${string.hashes}`, index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(Math.min(index + string.hashes.length + 2, source.length)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1140
to
+1151
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. End single-line strings at a newline so one stray quote cannot hide later A single-line ( A trigger is a bare-slash regex literal such as This is the same class of problem as the earlier hash-regex finding, so a generic recovery is better than adding one more delimiter. Pop a single-line string at the first Based on learnings: an audit gate must use syntax-aware matching, because partial pattern coverage lets real violations pass as a clean report. Proposed fix
Committable suggestion
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (source.startsWith("//", index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newline = source.indexOf("\n", index); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(newline === -1 ? source.length : newline); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (source.startsWith("/*", index)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blockDepth = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (source[index] === '"' || source[index] === "#") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const regexOpening = /^(#+)\//.exec(source.slice(index)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const opening = /^(#*)("{3}|")/.exec(source.slice(index)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (regexOpening) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| regexEnd = `/${regexOpening[1]}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + regexOpening[0].length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (opening) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strings.push({ end: opening[2] + opening[1], hashes: opening[1], interpolationDepth: 0 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + opening[0].length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
genesiscz marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (string && source[index] === "(") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| string.interpolationDepth++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (string && source[index] === ")") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| string.interpolationDepth--; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blank(index + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return masked.join(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Swift has no grammar in the ast-grep napi package, so its rules are line regexes. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function checkSwiftSource(file: string, source: string): Finding[] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const lines = source.split("\n"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const codeLines = maskSwiftNonCode(source).split("\n"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const findings: Finding[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const [index, text] of lines.entries()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1091,7 +1194,7 @@ export function checkSwiftSource(file: string, source: string): Finding[] { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const { rule, pattern } of SWIFT_LINE_RULES) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const column = text.search(pattern); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const column = (rule === SWIFT_ENTRY_MACRO_RULE ? codeLines[index] : text).search(pattern); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (column === -1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.