getreadystack's picture
Upload index.html with huggingface_hub
d64c10d verified
Raw History Blame Contribute Delete
41.7 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Swift 6 language mode — find strict concurrency blockers</title>
<meta name="description" content="This browser tool finds strict concurrency blockers in Swift files before you change the language mode.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/swift6-concurrency-lint"></head>
<body>
<div class="wrap">
<h1>Swift 6 language mode — find strict concurrency blockers</h1>
<p class="lede">Finds the Swift 6 strict-concurrency blockers in your .swift files — global var, static var shared, @unchecked Sendable, DispatchQueue.main.async inside async — before you flip the language mode. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">// swift-tools-version: 5.9
// SessionStore.swift — written fast, compiles under Swift 5, stops compiling under Swift 6.
@preconcurrency import Foundation
import Combine
var sharedCache: [String: Data] = [:]
var requestCount = 0
final class SessionStore: @unchecked Sendable {
static var shared = SessionStore()
nonisolated(unsafe) static var lastToken: String?
private let queue = DispatchQueue(label: "session.store")
private var tokens: [String] = []
func token(for user: String) -&gt; String? {
return queue.sync {
return tokens.first
}
}
func refresh(completion: @escaping (Result&lt;String, Error&gt;) -&gt; Void) {
Task.detached {
let value = await self.fetchToken()
DispatchQueue.main.async {
completion(.success(value))
}
}
}
func blockingToken() -&gt; String {
let semaphore = DispatchSemaphore(value: 0)
var result = ""
Task {
result = await fetchToken()
semaphore.signal()
}
semaphore.wait()
return result
}
private func fetchToken() async -&gt; String {
requestCount += 1
return "token-\(requestCount)"
}
}</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Lints the .swift files you open and names every Swift 6 language-mode blocker with its line and the fix. <strong>In the editor:</strong> Exports one Swift 6 migration report for the whole repository — every file ranked, CI-readable, yours to hand to the team — that part asks for a licence key.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var SWIFT6_RULES = [
{"check": "global_var", "sev": "error", "pattern": "^(?:public |package |internal |private |fileprivate )?var\\s+[A-Za-z_][A-Za-z0-9_]*", "msg": "Top-level 'var' is not concurrency-safe under Swift 6 — the compiler rejects it.", "fix": "Make it 'let', or move it inside an actor / mark it @MainActor."},
{"check": "static_var", "sev": "error", "pattern": "\\bstatic\\s+var\\s+[A-Za-z_]", "not": "\\blet\\b\\s*=\\s*$", "msg": "'static var' is a mutable global — the classic 'static var shared' singleton stops compiling in Swift 6 language mode.", "fix": "Use 'static let' for an immutable instance, or declare the type an actor / @MainActor final class."},
{"check": "unchecked_sendable", "sev": "warn", "pattern": "@unchecked\\s+Sendable", "msg": "'@unchecked Sendable' silences the checker without removing the race.", "fix": "Keep it only next to a comment naming the lock that protects every stored property, or convert the type to an actor."},
{"check": "nonisolated_unsafe", "sev": "warn", "pattern": "nonisolated\\(unsafe\\)", "msg": "'nonisolated(unsafe)' is a migration escape hatch, not a fix — it opts the declaration out of isolation checking.", "fix": "Track it as debt: isolate the storage to an actor or make it immutable."},
{"check": "preconcurrency_import", "sev": "info", "pattern": "@preconcurrency\\s+import", "msg": "'@preconcurrency import' downgrades Sendable errors from that module to warnings.", "fix": "Re-check this import when the dependency ships its own Sendable annotations; the shim hides real errors until then."},
{"check": "dispatch_main_in_async", "sev": "warn", "pattern": "DispatchQueue\\.main\\.async", "needs": "\\b(?:await|async)\\b", "msg": "'DispatchQueue.main.async' inside a file that already uses async/await hops the main thread without telling the isolation checker.", "fix": "Use '@MainActor' on the function or 'await MainActor.run { }'."},
{"check": "detached_task", "sev": "warn", "pattern": "Task\\.detached", "msg": "'Task.detached' drops the enclosing actor isolation and priority, so captured state crosses an isolation boundary unchecked.", "fix": "Use plain 'Task { }' unless you truly need to leave the actor; then pass only Sendable values."},
{"check": "escaping_not_sendable", "sev": "warn", "pattern": "@escaping", "not": "@Sendable", "msg": "'@escaping' closure without '@Sendable' — if it is called from another isolation domain Swift 6 rejects the call site.", "fix": "Write '@escaping @Sendable' or replace the completion handler with an 'async' function."},
{"check": "dispatch_semaphore", "sev": "error", "pattern": "DispatchSemaphore", "msg": "DispatchSemaphore blocks a cooperative-pool thread; inside async code that can deadlock the whole pool.", "fix": "Await the value instead (async/await, AsyncStream, or 'withCheckedContinuation')."},
{"check": "queue_sync_lock", "sev": "warn", "pattern": "\\.sync\\s*\\{", "msg": "Serial-queue '.sync { }' is the Swift 5 locking pattern; it is invisible to the Swift 6 isolation checker.", "fix": "Replace the queue with an actor, or use 'Mutex' / 'OSAllocatedUnfairLock' which are Sendable-aware."},
{"check": "tools_version_5", "sev": "error", "pattern": "swift-tools-version:\\s*5\\.", "msg": "Package.swift is pinned to tools version 5.x, so the package never builds in Swift 6 language mode no matter how the source looks.", "fix": "Raise it to 'swift-tools-version: 6.0' (or set swiftLanguageMode(.v6) per target) and fix the errors that appear."}
]
;</script>
<script>/* Swift 6 Concurrency Migration Lint — one brain, used by the VS Code extension and by the free web page. */
(function (root) {
'use strict';
var RULES = (typeof module !== 'undefined' && module.exports)
? require('./rules.json')
: (root.SWIFT6_RULES || []);
function compiled(rule) {
if (!rule._re) {
rule._re = new RegExp(rule.pattern);
rule._not = rule.not ? new RegExp(rule.not) : null;
rule._needs = rule.needs ? new RegExp(rule.needs) : null;
}
return rule;
}
function isComment(line) {
var t = line.replace(/^\s+/, '');
return t.indexOf('//') === 0 || t.indexOf('*') === 0 || t.indexOf('/*') === 0;
}
function check(text, opts) {
opts = opts || {};
var src = String(text == null ? '' : text);
var lines = src.split(/\r?\n/);
var findings = [];
for (var r = 0; r < RULES.length; r++) {
var rule = compiled(RULES[r]);
if (rule._needs && !rule._needs.test(src)) continue;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (isComment(line) && rule.check !== 'tools_version_5') continue;
if (!rule._re.test(line)) continue;
if (rule._not && rule._not.test(line)) continue;
findings.push({
check: rule.check,
sev: rule.sev,
msg: rule.msg,
fix: rule.fix,
line: i + 1,
text: line.replace(/^\s+/, '').slice(0, 120)
});
}
}
findings.sort(function (a, b) { return a.line - b.line; });
return { findings: findings, today: opts.today || '', rule_count: RULES.length };
}
var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
root.SWIFT6ENGINE = api;
if (typeof module !== 'undefined' && module.exports) module.exports = api;
})(typeof window !== 'undefined' ? window : globalThis);
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var SWIFT6REPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = SWIFT6REPORT;
else window.SWIFT6REPORT = SWIFT6REPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.SWIFT6ENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.SWIFT6REPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.swift6-concurrency-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'swift6-concurrency-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/swift6-concurrency-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'swift6-concurrency-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/swift6-concurrency-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'swift6-concurrency-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://hub.docker.com/r/getreadystack/swift6-concurrency-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'swift6-concurrency-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.swift6-concurrency-lint
npx @readystack/swift6-concurrency-lint &lt;file&gt;
docker run --rm -v &quot;$PWD:/w&quot; getreadystack/swift6-concurrency-lint /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 11 rules Swift 6 Concurrency Migration Lint checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"swift6-concurrency-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">11 rules that name the .swift lines Swift 6 language mode rejects — 13 findings, 6 build-blocking, on the 47-line fixture</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">One hour of a contract iOS engineer, at the $100/hour rates common in the US market, costs more than this licence does once.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_BFREQqiq0OZmwx6LyfcXtZSHDTngmgIAiYpyj1yj7Ca" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script>
<div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-18">Updated 2026-09-18</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-18","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku109792_result_card.jpg" alt="Swift 6 Concurrency Migration Lint" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Eleven rules read a 47-line SessionStore.swift and returned thirteen findings, six of them build-blocking errors — six lines that compile today under Swift 5 and stop compiling the moment that target moves to Swift 6 language mode. The file is not exotic. It is the shape every iOS and macOS codebase has somewhere: a shared session store, a token cache, a completion handler, a queue used as a lock.</p>
<p>Here is what the six errors were. A top-level <code>var sharedCache: [String: Data] = [:]</code>, which Swift 6 rejects because a mutable global is reachable from every isolation domain at once. A second top-level <code>var requestCount = 0</code>. A <code>static var shared = SessionStore()</code> — the singleton spelling that appears in almost every tutorial written before 2024. A <code>nonisolated(unsafe) static var lastToken</code>, which is the same global wearing an escape hatch. A <code>DispatchSemaphore</code> used to turn an async call back into a blocking one, which parks a cooperative-pool thread and can deadlock the pool. And, one line above all of it, <code>// swift-tools-version: 5.9</code> — the line that decides whether the package can enter Swift 6 language mode at all.</p>
<p>That last one is the reason a chatbot cannot finish this job. You paste a Swift file and ask whether it is Swift 6 ready; the model answers about the file. It never sees Package.swift, so it cannot tell you that the answer is moot until the tools version moves to 6.0. The lint reads <code>**/*.swift</code>, and Package.swift is a .swift file.</p>
<p>The seven remaining findings were warnings and one note: <code>@unchecked Sendable</code> on the class, which silences the checker without removing the race; <code>queue.sync { }</code> around the token array, the Swift 5 locking pattern that the isolation checker cannot see; <code>@escaping</code> on a completion handler with no <code>@Sendable</code>, which fails at the call site as soon as it is called from another domain; <code>Task.detached</code>, which drops the surrounding actor isolation; <code>DispatchQueue.main.async</code> inside a file that already uses <code>await</code>; and <code>@preconcurrency import Foundation</code>, the shim that downgrades the module&#x27;s own Sendable errors to warnings while you migrate.</p>
<p>Assistant-written Swift concentrates these. The public Swift corpus is mostly pre-concurrency, so the default draft is a <code>static var shared</code> with a serial queue and a completion handler — correct Swift 5, six errors in Swift 6. The code review that catches it is a line-by-line read of every file, which is exactly the work nobody schedules.</p>
<p>The rewritten version of the same file — an <code>actor SessionStore</code>, a <code>Sendable</code> struct for the token, <code>static let shared</code>, <code>@MainActor</code> on the view type, <code>await</code> instead of the semaphore — returns zero findings from the same eleven rules. Both files ship with the extension, so the numbers here can be reproduced.</p>
<p>Four rules are errors (<code>global_var</code>, <code>static_var</code>, <code>dispatch_semaphore</code>, <code>tools_version_5</code>), six are warnings, one is informational. Every finding carries a line number, the reason the compiler objects, and the replacement shape.</p>
<p>The free extension lints the files you open and names every blocker; that job finishes with no licence key, no watermark, no usage counter. The paid tier — every file ranked by blocking errors, committable, readable in CI. One hour of a contract iOS engineer, at the $100/hour rates common in the US market, costs more than that licence does once. The same engine also runs in a single browser page with nothing uploaded: https://getreadystack.com/tools/swift6-concurrency-lint</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku109792_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku109792_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Swift 6 Concurrency Migration Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads your .swift files with 11 rules and lists every line that Swift 6 language mode rejects or quietly leaves racy: top-level var, static var singletons, @unchecked Sendable, nonisolated(unsafe), DispatchSemaphore, serial-queue locking, @escaping without @Sendable, and a Package.swift still pinned to swift-tools-version 5.x. Each finding carries a line number and the replacement shape.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">iOS and macOS developers who have a working Swift 5 codebase and a Swift 6 migration in front of them, especially teams whose recent code was drafted with an assistant. Assistants still emit the Swift 5 singleton and completion-handler shapes by default, so those files carry the largest share of blockers when the language mode is switched on.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask a chatbot or turn on the compiler flag?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A chatbot sees only the file you paste, so it never reads the swift-tools-version line in Package.swift that decides whether Swift 6 mode can be enabled at all. Flipping the flag gives you a wall of compiler errors in build order, not in triage order, and it says nothing about the warnings that leave a race in place.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what costs money?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free: lint the .swift files you open, with every blocker named, its line number shown, and its fix described. That job finishes with no licence key, no watermark and no usage counter. Paid at $29 once: export one migration report covering the whole repository, every file ranked by blocking errors, in a form you can commit or read in CI.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost without the tool?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The alternative is engineer hours. One hour of a contract iOS engineer, at the $100/hour rates common in the US market, already costs more than the licence, and a manual triage of a mid-sized app means opening every file to look for globals, singletons and semaphores by eye. The lint returns the same list in one pass.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Swift 6 Concurrency Lint - strict concurrency migration checks the file you open against 11 rules from a rule set dated 2026-09-18, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Swift 6 Concurrency Migration Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads your .swift files with 11 rules and lists every line that Swift 6 language mode rejects or quietly leaves racy: top-level var, static var singletons, @unchecked Sendable, nonisolated(unsafe), DispatchSemaphore, serial-queue locking, @escaping without @Sendable, and a Package.swift still pinned to swift-tools-version 5.x. Each finding carries a line number and the replacement shape."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "iOS and macOS developers who have a working Swift 5 codebase and a Swift 6 migration in front of them, especially teams whose recent code was drafted with an assistant. Assistants still emit the Swift 5 singleton and completion-handler shapes by default, so those files carry the largest share of blockers when the language mode is switched on."}}, {"@type": "Question", "name": "Why not just ask a chatbot or turn on the compiler flag?", "acceptedAnswer": {"@type": "Answer", "text": "A chatbot sees only the file you paste, so it never reads the swift-tools-version line in Package.swift that decides whether Swift 6 mode can be enabled at all. Flipping the flag gives you a wall of compiler errors in build order, not in triage order, and it says nothing about the warnings that leave a race in place."}}, {"@type": "Question", "name": "What is free and what costs money?", "acceptedAnswer": {"@type": "Answer", "text": "Free: lint the .swift files you open, with every blocker named, its line number shown, and its fix described. That job finishes with no licence key, no watermark and no usage counter. Paid at $29 once: export one migration report covering the whole repository, every file ranked by blocking errors, in a form you can commit or read in CI."}}, {"@type": "Question", "name": "What would this cost without the tool?", "acceptedAnswer": {"@type": "Answer", "text": "The alternative is engineer hours. One hour of a contract iOS engineer, at the $100/hour rates common in the US market, already costs more than the licence, and a manual triage of a mid-sized app means opening every file to look for globals, singletons and semaphores by eye. The lint returns the same list in one pass."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Swift 6 Concurrency Lint - strict concurrency migration checks the file you open against 11 rules from a rule set dated 2026-09-18, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:109792,slug:"swift6-concurrency-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/devicetree-config-language-pack"><span class="yj-rel-t">DeviceTree Config Audit: 23 dtc-Style Rules, 50 Snippets</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/robots-txt-ai-crawler-audit"><span class="yj-rel-t">AI Crawler Rules - robots.txt Audit for AI Search</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bulk-sender-lint"><span class="yj-rel-t">Bulk Sender Lint - the Gmail, Yahoo and Outlook.com rules, checked in your mai</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/consent-mode-v2-lint"><span class="yj-rel-t">Consent Mode v2 Lint for GA4 &amp; Google Ads</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/en301549-lint"><span class="yj-rel-t">EN 301 549 Lint — European Accessibility Act rules, with the clause number, in</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ai-act-article-50-audit"><span class="yj-rel-t">EU AI Act Article 50 Transparency Audit for Your Codebase</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>