feat: Add comprehensive Swift language detection support (#223)

* feat: Add comprehensive Swift language detection support

Add Swift language detection with 40+ patterns covering syntax, stdlib, frameworks, and idioms. Implement fork-friendly architecture with separate swift_patterns.py module and graceful import fallback.

Key changes:
- New swift_patterns.py: 40+ Swift detection patterns (SwiftUI, Combine, async/await, property wrappers, etc.)
- Enhanced language_detector.py: Graceful import handling, robust pattern compilation with error recovery
- Comprehensive test suite: 19 tests covering syntax, frameworks, edge cases, and error handling
- Updated .gitignore: Exclude Claude-specific config files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Fix Swift pattern false positives and add comprehensive error handling

Critical Fixes (Priority 0):
- Fix 'some' and 'any' keyword false positives by requiring capitalized type names
- Use (?-i:[A-Z]) to enforce case-sensitivity despite global IGNORECASE flag
- Prevents "some random" from being detected as Swift code

Error Handling (Priority 1):
- Wrap pattern validation in try/except to prevent module import crashes
- Add SWIFT_PATTERNS verification with logging after import
- Gracefully degrade to empty dict on validation errors
- Add 7 comprehensive error handling tests

Improvements (Priority 2):
- Remove fragile line number references in comments
- Add 5 new tests for previously untested patterns:
  * Property observers (willSet/didSet)
  * Memory management (weak var, unowned, [weak self])
  * String interpolation

Test Results:
- All 92 tests passing (72 Swift + 20 language detection)
- Fixed regression: test_detect_unknown now passes
- 12 new tests added (7 error handling + 5 feature coverage)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Edinho
2026-01-01 17:25:53 +01:00
committed by GitHub
parent e26ba876ae
commit 98d73611ad
4 changed files with 2088 additions and 4 deletions

View File

@@ -9,8 +9,45 @@ Author: Skill Seekers Project
"""
import re
import logging
from typing import Optional, Tuple, Dict, List
logger = logging.getLogger(__name__)
# Import Swift patterns from separate module (fork-friendly architecture)
try:
from skill_seekers.cli.swift_patterns import SWIFT_PATTERNS
except ImportError as e:
logger.warning(
"Swift language detection patterns unavailable. "
"Swift code detection will be disabled. Error: %s",
e
)
SWIFT_PATTERNS: Dict[str, List[Tuple[str, int]]] = {}
except Exception as e:
logger.error(
"Failed to load Swift patterns due to unexpected error: %s. "
"Swift detection disabled.",
e
)
SWIFT_PATTERNS: Dict[str, List[Tuple[str, int]]] = {}
# Verify Swift patterns were loaded correctly
if not SWIFT_PATTERNS:
logger.warning(
"Swift pattern dictionary is empty. Swift detection is disabled. "
"This may indicate swift_patterns.py has no patterns defined."
)
elif 'swift' not in SWIFT_PATTERNS:
logger.error(
"Swift patterns loaded but 'swift' key is missing. "
"Swift detection is broken. Please file a bug report."
)
else:
logger.info(
"Swift patterns loaded successfully: %d patterns for language detection",
len(SWIFT_PATTERNS.get('swift', []))
)
# Comprehensive language patterns with weighted confidence scoring
# Weight 5: Unique identifiers (highly specific)
@@ -371,6 +408,9 @@ LANGUAGE_PATTERNS: Dict[str, List[Tuple[str, int]]] = {
],
}
# Merge Swift patterns (fork-friendly: patterns defined in swift_patterns.py)
LANGUAGE_PATTERNS.update(SWIFT_PATTERNS)
# Known language list for CSS class detection
KNOWN_LANGUAGES = [
@@ -418,10 +458,32 @@ class LanguageDetector:
def _compile_patterns(self) -> None:
"""Compile regex patterns and cache them for performance"""
for lang, patterns in LANGUAGE_PATTERNS.items():
self._pattern_cache[lang] = [
(re.compile(pattern, re.IGNORECASE | re.MULTILINE), weight)
for pattern, weight in patterns
]
compiled_patterns = []
for i, (pattern, weight) in enumerate(patterns):
try:
compiled = re.compile(pattern, re.IGNORECASE | re.MULTILINE)
compiled_patterns.append((compiled, weight))
except re.error as e:
logger.error(
"Invalid regex pattern for language '%s' at index %d: '%s'. "
"Error: %s. Pattern skipped.",
lang, i, pattern[:50], e
)
except TypeError as e:
logger.error(
"Pattern for language '%s' at index %d is not a string: %s. "
"Pattern skipped.",
lang, i, type(pattern).__name__
)
if compiled_patterns:
self._pattern_cache[lang] = compiled_patterns
else:
logger.warning(
"No valid patterns compiled for language '%s'. "
"Detection for this language is disabled.",
lang
)
def detect_from_html(self, elem, code: str) -> Tuple[str, float]:
"""

View File

@@ -0,0 +1,621 @@
#!/usr/bin/env python3
"""
Swift Language Detection Patterns
Comprehensive regex patterns for detecting Swift code including:
- Pure Swift syntax (structs, protocols, extensions, optionals, generics)
- iOS/UIKit patterns (UIViewController, IBOutlet, lifecycle methods)
- macOS/AppKit patterns (NSViewController, NSWindow, AppKit classes)
- SwiftUI patterns (@State, @Binding, View protocol, modifiers)
- Combine framework patterns (Publishers, Subscribers)
- Swift Concurrency (async/await, actors, Tasks)
- Foundation Models (iOS/macOS 26+: @Generable, LanguageModelSession, SystemLanguageModel)
Weight Scale:
- 5: Unique to Swift (no other language uses this)
- 4: Strong Swift indicator (rarely seen elsewhere)
- 3: Common Swift pattern (moderate indicator)
- 2: Moderate indicator (seen in some other languages)
- 1: Weak indicator (commonly seen elsewhere)
Author: iLearn Project (Swift Support Extension)
"""
import logging
from typing import Dict, List, Tuple
logger = logging.getLogger(__name__)
SWIFT_PATTERNS: Dict[str, List[Tuple[str, int]]] = {
'swift': [
# ===== PURE SWIFT SYNTAX (Weight 4-5) =====
# Function declarations with return type arrow (Swift-specific syntax)
(r'\bfunc\s+\w+\s*\([^)]*\)\s*->', 5), # func foo() -> ReturnType
(r'\bfunc\s+\w+\s*\([^)]*\)\s*\{', 4), # func foo() {
# Struct/class/enum declarations
(r'\bstruct\s+\w+\s*:', 5), # struct Foo: Protocol
(r'\bstruct\s+\w+\s*\{', 4), # struct Foo {
(r'\bclass\s+\w+\s*:\s*\w+', 4), # class Foo: SuperClass
(r'\benum\s+\w+\s*:\s*\w+', 4), # enum Foo: String
(r'\benum\s+\w+\s*\{', 3), # enum Foo {
# Protocol declarations (weight 5 - Swift-specific in this context)
(r'\bprotocol\s+\w+\s*\{', 5), # protocol Foo {
(r'\bprotocol\s+\w+\s*:\s*\w+', 5), # protocol Foo: AnotherProtocol
# Extension (weight 5 - very Swift-specific syntax)
(r'\bextension\s+\w+\s*:', 5), # extension Foo: Protocol
(r'\bextension\s+\w+\s*\{', 5), # extension Foo {
(r'\bextension\s+\w+\s*where\s+', 5), # extension Foo where T: Equatable
# Property wrappers (weight 5 - unique to Swift)
(r'@\w+\s+var\s+\w+', 4), # @Something var foo
(r'@propertyWrapper', 5), # @propertyWrapper attribute
# Optionals and unwrapping (weight 5 - unique to Swift)
(r'\bguard\s+let\s+\w+\s*=', 5), # guard let foo =
(r'\bguard\s+var\s+\w+\s*=', 5), # guard var foo =
(r'\bif\s+let\s+\w+\s*=', 5), # if let foo =
(r'\bif\s+var\s+\w+\s*=', 5), # if var foo =
(r'\bguard\s+.*\s+else\s*\{', 4), # guard ... else {
(r'\w+\?\.', 4), # foo?.bar (optional chaining)
(r'\w+!\.\w+', 4), # foo!.bar (forced unwrap)
(r'\?\?', 4), # nil coalescing ??
# Closures (weight 4-5 - Swift closure syntax)
(r'\{\s*\([^)]*\)\s*in\b', 5), # { (params) in
(r'\{\s*\$0', 4), # { $0 (shorthand argument)
(r'\$[0-9]+', 3), # $0, $1, etc.
# Type annotations (weight 3-4)
(r':\s*\[\w+\]', 4), # : [Type] (array type)
(r':\s*\[\w+\s*:\s*\w+\]', 4), # : [Key: Value] (dictionary type)
(r':\s*\w+\?', 4), # : Type? (optional type)
(r'->\s*\w+\?', 4), # -> Type? (optional return)
# Keywords (weight 2-4)
(r'\blet\s+\w+\s*:', 3), # let foo: Type
(r'\bvar\s+\w+\s*:', 3), # var foo: Type
(r'\blet\s+\w+\s*=', 2), # let foo = (also JS const)
(r'\bself\.\w+', 3), # self.property (also Python)
(r'\bSelf\b', 4), # Self (Swift-specific)
# Imports (weight 2-5 based on specificity)
(r'\bimport\s+Foundation', 4), # import Foundation
(r'\bimport\s+\w+', 2), # import Something
# Access modifiers (weight 3-5)
(r'\bprivate\s*\(set\)', 5), # private(set) - Swift-specific
(r'\bfileprivate\s+', 5), # fileprivate - Swift-only keyword
(r'\binternal\s+', 2), # internal (also C#)
(r'\bopen\s+class', 4), # open class (Swift-specific)
# Error handling (weight 4-5)
(r'\bthrows\s*->', 5), # throws -> ReturnType
(r'\bthrows\s*\{', 4), # func foo() throws {
(r'\brethrows\b', 5), # rethrows keyword (Swift-only)
(r'\btry\?\s+', 5), # try? (optional try - Swift)
(r'\btry!\s+', 5), # try! (forced try - Swift)
(r'\bdo\s*\{', 3), # do { (try block)
(r'\bcatch\s+\w+', 3), # catch error
# Generics (weight 3-4)
(r'<\w+:\s*\w+>', 4), # <T: Protocol>
(r'\bwhere\s+\w+\s*:', 4), # where T: Protocol
# NOTE: 'some' and 'any' require capitalized type names to avoid matching
# English prose ("some example", "any variable"). Swift types are capitalized
# by convention (View, Protocol, etc.). The (?-i:[A-Z]) syntax enforces
# case-sensitive matching for the first letter despite global IGNORECASE flag.
(r'\bsome\s+(?-i:[A-Z])\w+', 5), # some Protocol (opaque type - Swift 5.1+)
(r'\bany\s+(?-i:[A-Z])\w+', 4), # any Protocol (existential type - Swift 5.6+)
# Actors and concurrency (weight 5 - Swift 5.5+)
(r'\bactor\s+\w+', 5), # actor MyActor
(r'\basync\s+throws', 5), # async throws (Swift-specific combo)
(r'\basync\s+func', 5), # async func
(r'\bawait\s+\w+', 3), # await (also JS/Python/C#)
(r'\bTask\s*\{', 4), # Task { (Swift concurrency)
(r'\bTask\.\w+', 4), # Task.detached, Task.sleep
(r'@MainActor', 5), # @MainActor attribute
(r'@Sendable', 5), # @Sendable attribute
(r'\bnonisolated\b', 5), # nonisolated keyword
# Result builders (weight 5)
(r'@resultBuilder', 5), # @resultBuilder attribute
# Type aliases and associated types (weight 5)
(r'\btypealias\s+\w+\s*=', 5), # typealias Foo = Bar
(r'\bassociatedtype\s+\w+', 5), # associatedtype Element
# Print function
(r'\bprint\s*\(', 2), # print() (also Python)
# String interpolation (weight 4)
(r'\\\(\w+', 4), # \(variable) interpolation
# Memory management (weight 4-5)
(r'\bweak\s+var', 5), # weak var
(r'\bunowned\s+', 5), # unowned (Swift-specific)
(r'\[weak\s+self\]', 5), # [weak self] capture list
(r'\[unowned\s+self\]', 5), # [unowned self] capture list
(r'\blazy\s+var', 4), # lazy var
# ===== iOS/UIKit PATTERNS (Weight 4-5) =====
(r'\bimport\s+UIKit', 5), # import UIKit
(r'\bUIViewController\b', 5), # UIViewController class
(r'\bUIView\b', 4), # UIView class
(r'\bUITableView\b', 5), # UITableView
(r'\bUICollectionView\b', 5), # UICollectionView
(r'\bUINavigationController\b', 5), # UINavigationController
(r'\bUITabBarController\b', 5), # UITabBarController
(r'\bUIButton\b', 4), # UIButton
(r'\bUILabel\b', 4), # UILabel
(r'\bUIImageView\b', 4), # UIImageView
(r'\bUITextField\b', 4), # UITextField
(r'\bUITextView\b', 4), # UITextView
(r'\bUIStackView\b', 4), # UIStackView
(r'\bUIScrollView\b', 4), # UIScrollView
(r'\bUIAlertController\b', 5), # UIAlertController
(r'\bUIApplication\b', 5), # UIApplication
(r'\bUIWindow\b', 4), # UIWindow
(r'\bUIScreen\b', 4), # UIScreen
(r'\bUIDevice\b', 5), # UIDevice
# UIKit Lifecycle methods (weight 5)
(r'\bviewDidLoad\s*\(\s*\)', 5), # viewDidLoad()
(r'\bviewWillAppear\s*\(', 5), # viewWillAppear(_:)
(r'\bviewDidAppear\s*\(', 5), # viewDidAppear(_:)
(r'\bviewWillDisappear\s*\(', 5), # viewWillDisappear(_:)
(r'\bviewDidDisappear\s*\(', 5), # viewDidDisappear(_:)
(r'\bviewWillLayoutSubviews\s*\(\)', 5), # viewWillLayoutSubviews()
(r'\bviewDidLayoutSubviews\s*\(\)', 5), # viewDidLayoutSubviews()
# Interface Builder outlets/actions (weight 5)
(r'@IBOutlet', 5), # @IBOutlet
(r'@IBAction', 5), # @IBAction
(r'@IBDesignable', 5), # @IBDesignable
(r'@IBInspectable', 5), # @IBInspectable
# UIKit delegates and datasources (weight 5)
(r'\bUITableViewDelegate\b', 5),
(r'\bUITableViewDataSource\b', 5),
(r'\bUICollectionViewDelegate\b', 5),
(r'\bUICollectionViewDataSource\b', 5),
(r'\bUITextFieldDelegate\b', 5),
(r'\bUIScrollViewDelegate\b', 5),
# Auto Layout (weight 4-5)
(r'\bNSLayoutConstraint\b', 5), # NSLayoutConstraint
(r'\.constraint\(', 4), # constraint methods
(r'\btranslatesAutoresizingMaskIntoConstraints', 5),
(r'NSLayoutConstraint\.activate', 5),
# GCD / DispatchQueue (weight 5)
(r'\bDispatchQueue\b', 5), # DispatchQueue
(r'\bDispatchQueue\.main', 5), # DispatchQueue.main
(r'\bDispatchQueue\.global', 5), # DispatchQueue.global
(r'\.async\s*\{', 4), # .async {
(r'\.sync\s*\{', 4), # .sync {
# ===== macOS/AppKit PATTERNS (Weight 4-5) =====
(r'\bimport\s+AppKit', 5), # import AppKit
(r'\bimport\s+Cocoa', 5), # import Cocoa
(r'\bNSViewController\b', 5), # NSViewController
(r'\bNSView\b', 4), # NSView
(r'\bNSWindow\b', 5), # NSWindow
(r'\bNSWindowController\b', 5), # NSWindowController
(r'\bNSApplication\b', 5), # NSApplication
(r'\bNSTableView\b', 5), # NSTableView
(r'\bNSOutlineView\b', 5), # NSOutlineView
(r'\bNSCollectionView\b', 5), # NSCollectionView
(r'\bNSButton\b', 4), # NSButton
(r'\bNSTextField\b', 4), # NSTextField
(r'\bNSTextView\b', 4), # NSTextView
(r'\bNSImageView\b', 4), # NSImageView
(r'\bNSStackView\b', 4), # NSStackView
(r'\bNSScrollView\b', 4), # NSScrollView
(r'\bNSSplitView\b', 5), # NSSplitView
(r'\bNSTabView\b', 5), # NSTabView
(r'\bNSMenu\b', 5), # NSMenu
(r'\bNSMenuItem\b', 5), # NSMenuItem
(r'\bNSToolbar\b', 5), # NSToolbar
(r'\bNSAlert\b', 5), # NSAlert
(r'\bNSPanel\b', 5), # NSPanel
(r'\bNSOpenPanel\b', 5), # NSOpenPanel
(r'\bNSSavePanel\b', 5), # NSSavePanel
(r'\bNSWorkspace\b', 5), # NSWorkspace
(r'\bNSRunningApplication\b', 5), # NSRunningApplication
(r'\bNSScreen\b', 4), # NSScreen
(r'\bNSColor\b', 4), # NSColor
(r'\bNSFont\b', 4), # NSFont
(r'\bNSImage\b', 4), # NSImage
(r'\bNSBezierPath\b', 5), # NSBezierPath
(r'\bNSSound\b', 5), # NSSound
(r'\bNSEvent\b', 5), # NSEvent
(r'\bNSResponder\b', 5), # NSResponder
(r'\bNSPasteboard\b', 5), # NSPasteboard
(r'\bNSStatusBar\b', 5), # NSStatusBar
(r'\bNSStatusItem\b', 5), # NSStatusItem
# macOS Lifecycle methods (weight 5)
# Note: viewDidLoad() is defined in the UIKit section above since it's shared
# between iOS (UIViewController) and macOS (NSViewController)
(r'\bviewWillAppear\s*\(\)', 5), # NSViewController viewWillAppear
(r'\bviewDidAppear\s*\(\)', 5), # NSViewController viewDidAppear
(r'\bawakeFromNib\s*\(\)', 5), # awakeFromNib()
(r'\bapplicationDidFinishLaunching', 5), # NSApplicationDelegate
(r'\bapplicationWillTerminate', 5), # NSApplicationDelegate
(r'\bwindowDidLoad\s*\(\)', 5), # NSWindowController
# macOS delegates (weight 5)
(r'\bNSTableViewDelegate\b', 5),
(r'\bNSTableViewDataSource\b', 5),
(r'\bNSOutlineViewDelegate\b', 5),
(r'\bNSOutlineViewDataSource\b', 5),
(r'\bNSWindowDelegate\b', 5),
(r'\bNSApplicationDelegate\b', 5),
(r'\bNSTextFieldDelegate\b', 5),
(r'\bNSTextViewDelegate\b', 5),
# ===== SwiftUI PATTERNS (Weight 5) =====
(r'\bimport\s+SwiftUI', 5), # import SwiftUI
(r'\bstruct\s+\w+\s*:\s*View', 5), # struct Foo: View
(r'\bvar\s+body\s*:\s*some\s+View', 5), # var body: some View
(r':\s*some\s+View', 5), # : some View
# SwiftUI property wrappers (weight 5 - unique to SwiftUI)
(r'@State\s+', 5), # @State var
(r'@Binding\s+', 5), # @Binding var
(r'@Published\s+', 5), # @Published var
(r'@ObservedObject\s+', 5), # @ObservedObject var
(r'@StateObject\s+', 5), # @StateObject var
(r'@EnvironmentObject\s+', 5), # @EnvironmentObject var
(r'@Environment\s*\(', 5), # @Environment(\.keyPath)
(r'@FetchRequest\s*\(', 5), # @FetchRequest (Core Data)
(r'@AppStorage\s*\(', 5), # @AppStorage
(r'@SceneStorage\s*\(', 5), # @SceneStorage
(r'@FocusState\s+', 5), # @FocusState
(r'@FocusedBinding\s*\(', 5), # @FocusedBinding
(r'@Observable\b', 5), # @Observable (Swift 5.9+)
(r'@Bindable\s+', 5), # @Bindable (Swift 5.9+)
(r'@Query\s*\(', 5), # @Query (SwiftData)
(r'@Model\b', 5), # @Model (SwiftData)
(r'@ViewBuilder', 5), # @ViewBuilder
# SwiftUI Views (weight 4-5)
(r'\bText\s*\(', 4), # Text("Hello")
(r'\bImage\s*\(', 3), # Image(systemName:)
(r'\bButton\s*\(', 3), # Button("Label") { }
(r'\bVStack\s*[\(\{]', 5), # VStack { } or VStack(alignment:)
(r'\bHStack\s*[\(\{]', 5), # HStack { }
(r'\bZStack\s*[\(\{]', 5), # ZStack { }
(r'\bList\s*[\(\{]', 4), # List { }
(r'\bForEach\s*\(', 4), # ForEach(items) { }
(r'\bNavigationView\s*\{', 5), # NavigationView { }
(r'\bNavigationStack\s*[\(\{]', 5), # NavigationStack { } (iOS 16+)
(r'\bNavigationSplitView\s*[\(\{]', 5), # NavigationSplitView (macOS/iPad)
(r'\bNavigationLink\s*\(', 5), # NavigationLink
(r'\bTabView\s*[\(\{]', 5), # TabView { }
(r'\bScrollView\s*[\(\{]', 5), # ScrollView { }
(r'\bLazyVStack\s*[\(\{]', 5), # LazyVStack { }
(r'\bLazyHStack\s*[\(\{]', 5), # LazyHStack { }
(r'\bLazyVGrid\s*\(', 5), # LazyVGrid
(r'\bLazyHGrid\s*\(', 5), # LazyHGrid
(r'\bGrid\s*[\(\{]', 4), # Grid { } (iOS 16+)
(r'\bGridRow\s*[\(\{]', 5), # GridRow { }
(r'\bGeometryReader\s*\{', 5), # GeometryReader { }
(r'\bSpacer\s*\(\)', 5), # Spacer()
(r'\bDivider\s*\(\)', 5), # Divider()
(r'\bForm\s*\{', 4), # Form { }
(r'\bSection\s*[\(\{]', 4), # Section { }
(r'\bGroup\s*\{', 4), # Group { }
(r'\bGroupBox\s*[\(\{]', 5), # GroupBox { }
(r'\bDisclosureGroup\s*\(', 5), # DisclosureGroup
(r'\bOutlineGroup\s*\(', 5), # OutlineGroup
(r'\bToggle\s*\(', 4), # Toggle
(r'\bPicker\s*\(', 4), # Picker
(r'\bSlider\s*\(', 4), # Slider
(r'\bStepper\s*\(', 4), # Stepper
(r'\bDatePicker\s*\(', 5), # DatePicker
(r'\bColorPicker\s*\(', 5), # ColorPicker
(r'\bProgressView\s*[\(\{]', 5), # ProgressView
(r'\bLabel\s*\(', 4), # Label
(r'\bLink\s*\(', 4), # Link
(r'\bMenu\s*[\(\{]', 4), # Menu
(r'\bContextMenu\s*\{', 5), # ContextMenu
(r'\bToolbar\s*\{', 5), # Toolbar
(r'\bToolbarItem\s*\(', 5), # ToolbarItem
(r'\bCanvas\s*\{', 5), # Canvas
(r'\bTimelineView\s*\(', 5), # TimelineView
(r'\bShareLink\s*\(', 5), # ShareLink (iOS 16+)
(r'\bPhotosPicker\s*\(', 5), # PhotosPicker
(r'\bTextField\s*\(', 4), # TextField
(r'\bSecureField\s*\(', 5), # SecureField
(r'\bTextEditor\s*\(', 5), # TextEditor
# SwiftUI Modifiers (weight 4-5)
(r'\.padding\s*\(', 4), # .padding()
(r'\.frame\s*\(', 4), # .frame(width:height:)
(r'\.foregroundColor\s*\(', 5), # .foregroundColor(.red)
(r'\.foregroundStyle\s*\(', 5), # .foregroundStyle (iOS 15+)
(r'\.background\s*\(', 3), # .background()
(r'\.cornerRadius\s*\(', 4), # .cornerRadius()
(r'\.clipShape\s*\(', 5), # .clipShape()
(r'\.shadow\s*\(', 3), # .shadow()
(r'\.font\s*\(', 3), # .font(.title)
(r'\.fontWeight\s*\(', 4), # .fontWeight()
(r'\.bold\s*\(\)', 4), # .bold()
(r'\.italic\s*\(\)', 4), # .italic()
(r'\.onAppear\s*\{', 5), # .onAppear { }
(r'\.onDisappear\s*\{', 5), # .onDisappear { }
(r'\.onTapGesture\s*\{', 5), # .onTapGesture { }
(r'\.gesture\s*\(', 4), # .gesture()
(r'\.sheet\s*\(', 5), # .sheet(isPresented:)
(r'\.fullScreenCover\s*\(', 5), # .fullScreenCover()
(r'\.popover\s*\(', 5), # .popover()
(r'\.alert\s*\(', 4), # .alert()
(r'\.confirmationDialog\s*\(', 5), # .confirmationDialog()
(r'\.navigationTitle\s*\(', 5), # .navigationTitle()
(r'\.navigationBarTitleDisplayMode', 5), # .navigationBarTitleDisplayMode
(r'\.toolbar\s*\{', 5), # .toolbar { }
(r'\.toolbarBackground\s*\(', 5), # .toolbarBackground()
(r'\.environmentObject\s*\(', 5), # .environmentObject()
(r'\.environment\s*\(', 4), # .environment()
(r'\.task\s*\{', 5), # .task { } (async)
(r'\.refreshable\s*\{', 5), # .refreshable { }
(r'\.searchable\s*\(', 5), # .searchable()
(r'\.onChange\s*\(', 5), # .onChange(of:)
(r'\.onSubmit\s*\{', 5), # .onSubmit { }
(r'\.focused\s*\(', 5), # .focused()
(r'\.disabled\s*\(', 4), # .disabled()
(r'\.opacity\s*\(', 3), # .opacity()
(r'\.offset\s*\(', 4), # .offset()
(r'\.rotationEffect\s*\(', 5), # .rotationEffect()
(r'\.scaleEffect\s*\(', 5), # .scaleEffect()
(r'\.animation\s*\(', 4), # .animation()
(r'\.transition\s*\(', 5), # .transition()
(r'\.withAnimation\s*\{', 5), # withAnimation { }
(r'\.matchedGeometryEffect\s*\(', 5), # .matchedGeometryEffect()
(r'\.contentShape\s*\(', 5), # .contentShape()
(r'\.allowsHitTesting\s*\(', 5), # .allowsHitTesting()
(r'\.overlay\s*\(', 4), # .overlay()
(r'\.mask\s*\(', 4), # .mask()
(r'\.zIndex\s*\(', 4), # .zIndex()
(r'\.layoutPriority\s*\(', 5), # .layoutPriority()
(r'\.preference\s*\(', 5), # .preference()
(r'\.onPreferenceChange\s*\(', 5), # .onPreferenceChange()
(r'\.coordinateSpace\s*\(', 5), # .coordinateSpace()
(r'\.ignoresSafeArea\s*\(', 5), # .ignoresSafeArea()
(r'\.safeAreaInset\s*\(', 5), # .safeAreaInset()
(r'\.listStyle\s*\(', 5), # .listStyle()
(r'\.buttonStyle\s*\(', 5), # .buttonStyle()
(r'\.textFieldStyle\s*\(', 5), # .textFieldStyle()
(r'\.pickerStyle\s*\(', 5), # .pickerStyle()
(r'\.labelStyle\s*\(', 5), # .labelStyle()
(r'\.toggleStyle\s*\(', 5), # .toggleStyle()
(r'\.presentationDetents\s*\(', 5), # .presentationDetents() (iOS 16+)
(r'\.interactiveDismissDisabled\s*\(', 5), # .interactiveDismissDisabled()
# SwiftUI Scene types (macOS/multi-window)
(r'\bWindowGroup\s*\{', 5), # WindowGroup { }
(r'\bWindow\s*\(', 5), # Window (macOS)
(r'\bSettings\s*\{', 5), # Settings { } (macOS)
(r'\bMenuBarExtra\s*\(', 5), # MenuBarExtra (macOS)
(r'\bDocumentGroup\s*\(', 5), # DocumentGroup
(r':\s*App\s*\{', 5), # : App {
(r'@main\b', 5), # @main
(r'var\s+body:\s*some\s+Scene', 5), # var body: some Scene
# ===== Combine Framework (Weight 5) =====
(r'\bimport\s+Combine', 5), # import Combine
(r'\bAnyPublisher\b', 5), # AnyPublisher
(r'\bPassthroughSubject\b', 5), # PassthroughSubject
(r'\bCurrentValueSubject\b', 5), # CurrentValueSubject
(r'\bPublisher\b', 4), # Publisher protocol
(r'\bSubscriber\b', 4), # Subscriber protocol
(r'\.sink\s*\{', 5), # .sink { }
(r'\.receive\s*\(on:\s*', 5), # .receive(on: RunLoop.main)
(r'\bAnyCancellable\b', 5), # AnyCancellable
(r'\.store\s*\(in:\s*&', 5), # .store(in: &cancellables)
(r'\.eraseToAnyPublisher\s*\(\)', 5), # .eraseToAnyPublisher()
(r'\.map\s*\{\s*\$0', 5), # .map { $0 (Combine map)
(r'\.flatMap\s*\{', 4), # .flatMap {
(r'\.compactMap\s*\{', 4), # .compactMap {
(r'\.filter\s*\{', 3), # .filter {
(r'\.debounce\s*\(', 5), # .debounce()
(r'\.throttle\s*\(', 5), # .throttle()
(r'\.removeDuplicates\s*\(', 5), # .removeDuplicates()
(r'\.combineLatest\s*\(', 5), # .combineLatest()
(r'\.merge\s*\(', 4), # .merge()
(r'\.zip\s*\(', 3), # .zip()
(r'@Published\s+var', 5), # @Published var
# ===== Codable/JSON (Weight 5) =====
(r'\bCodable\b', 5), # Codable protocol
(r'\bEncodable\b', 4), # Encodable protocol
(r'\bDecodable\b', 4), # Decodable protocol
(r'\bJSONDecoder\s*\(\)', 5), # JSONDecoder()
(r'\bJSONEncoder\s*\(\)', 5), # JSONEncoder()
(r'\bCodingKeys\b', 5), # CodingKeys enum
(r'\bPropertyListDecoder\b', 5), # PropertyListDecoder
(r'\bPropertyListEncoder\b', 5), # PropertyListEncoder
# ===== Core Data (Weight 5) =====
(r'\bimport\s+CoreData', 5), # import CoreData
(r'\bNSManagedObject\b', 5), # NSManagedObject
(r'\bNSManagedObjectContext\b', 5), # NSManagedObjectContext
(r'\bNSPersistentContainer\b', 5), # NSPersistentContainer
(r'\bNSFetchRequest\b', 5), # NSFetchRequest
(r'\b@FetchRequest\b', 5), # @FetchRequest property wrapper
(r'\bNSPredicate\b', 5), # NSPredicate
(r'\bNSSortDescriptor\b', 5), # NSSortDescriptor
# ===== SwiftData (Weight 5 - iOS 17+) =====
(r'\bimport\s+SwiftData', 5), # import SwiftData
(r'@Model\s+', 5), # @Model class
(r'@Attribute\s*\(', 5), # @Attribute
(r'@Relationship\s*\(', 5), # @Relationship
(r'\bModelContext\b', 5), # ModelContext
(r'\bModelContainer\b', 5), # ModelContainer
# ===== Common Apple Frameworks (Weight 4-5) =====
(r'\bimport\s+MapKit', 5), # import MapKit
(r'\bimport\s+CoreLocation', 5), # import CoreLocation
(r'\bimport\s+AVFoundation', 5), # import AVFoundation
(r'\bimport\s+Photos', 5), # import Photos
(r'\bimport\s+PhotosUI', 5), # import PhotosUI
(r'\bimport\s+HealthKit', 5), # import HealthKit
(r'\bimport\s+StoreKit', 5), # import StoreKit
(r'\bimport\s+CloudKit', 5), # import CloudKit
(r'\bimport\s+UserNotifications', 5), # import UserNotifications
(r'\bimport\s+EventKit', 5), # import EventKit
(r'\bimport\s+Contacts', 5), # import Contacts
(r'\bimport\s+MessageUI', 5), # import MessageUI
(r'\bimport\s+SafariServices', 5), # import SafariServices
(r'\bimport\s+WebKit', 5), # import WebKit
(r'\bimport\s+PDFKit', 5), # import PDFKit
(r'\bimport\s+QuickLook', 5), # import QuickLook
(r'\bimport\s+AuthenticationServices', 5), # import AuthenticationServices
(r'\bimport\s+LocalAuthentication', 5), # import LocalAuthentication
(r'\bimport\s+GameKit', 5), # import GameKit
(r'\bimport\s+SpriteKit', 5), # import SpriteKit
(r'\bimport\s+SceneKit', 5), # import SceneKit
(r'\bimport\s+RealityKit', 5), # import RealityKit
(r'\bimport\s+ARKit', 5), # import ARKit
(r'\bimport\s+Metal', 5), # import Metal
(r'\bimport\s+CoreML', 5), # import CoreML
(r'\bimport\s+Vision', 5), # import Vision
(r'\bimport\s+NaturalLanguage', 5), # import NaturalLanguage
(r'\bimport\s+Speech', 5), # import Speech
(r'\bimport\s+CoreBluetooth', 5), # import CoreBluetooth
(r'\bimport\s+NetworkExtension', 5), # import NetworkExtension
(r'\bimport\s+WidgetKit', 5), # import WidgetKit
(r'\bimport\s+ActivityKit', 5), # import ActivityKit
(r'\bimport\s+AppIntents', 5), # import AppIntents
# ===== Foundation Models Framework (iOS/macOS/visionOS 26+) =====
# Apple's on-device AI/ML framework for language model interactions
# Import statement
(r'\bimport\s+FoundationModels', 5), # import FoundationModels
# Core classes
(r'\bSystemLanguageModel\b', 5), # Main model class
(r'\bLanguageModelSession\b', 5), # Session for interactions
(r'\bLanguageModelFeedback\b', 5), # Feedback reporting
# Key structs
(r'\bInstructionsBuilder\b', 5), # Result builder for instructions
(r'\bPromptBuilder\b', 5), # Result builder for prompts
(r'\bGenerationOptions\b', 5), # Controls generation behavior
(r'\bGeneratedContent\b', 5), # Structured output type
(r'\bGenerationID\b', 5), # Unique generation identifier
(r'\bGenerationSchema\b', 5), # Schema for guided generation
(r'\bDynamicGenerationSchema\b', 5), # Runtime schema definition
(r'\bGenerationGuide\b', 5), # Value constraint guides
# Macros (unique to FoundationModels)
(r'@Generable\b', 5), # Guided generation macro
(r'@Generable\s*\(\s*description:', 5), # @Generable with description
(r'@Guide\b', 5), # Property constraint macro
(r'@Guide\s*\(\s*description:', 5), # @Guide with description
# Key protocols
(r'\bGenerable\b', 4), # Core protocol (also common word)
(r'\bInstructionsRepresentable\b', 5), # Instructions protocol
(r'\bPromptRepresentable\b', 5), # Prompt protocol
(r'\bConvertibleFromGeneratedContent\b', 5), # Content conversion
(r'\bConvertibleToGeneratedContent\b', 5), # Content conversion
# Nested types (SystemLanguageModel.*)
(r'\bSystemLanguageModel\.default\b', 5), # Default model access
(r'\bSystemLanguageModel\.UseCase\b', 5), # Use case type
(r'\bSystemLanguageModel\.Guardrails\b', 5), # Safety guardrails
(r'\bSystemLanguageModel\.Adapter\b', 5), # Custom adapters
(r'\bSystemLanguageModel\.Availability\b', 5), # Availability enum
# Key methods
(r'\.respond\s*\(to:', 4), # Primary response method
(r'\.respond\s*\([^)]*generating:', 5), # Guided generation response
(r'\.streamResponse\s*\(', 4), # Streaming response
(r'\.prewarm\s*\(', 5), # Session prewarming
(r'\.logFeedbackAttachment\s*\(', 5), # Feedback logging
# Transcript types
(r'\bTranscript\.Entry\b', 5), # Transcript entry
(r'\bTranscript\.Segment\b', 5), # Transcript segment
(r'\bTranscript\.ToolCall\b', 5), # Tool call record
(r'\bTranscript\.ToolOutput\b', 5), # Tool output record
(r'\bTranscript\.Response\b', 5), # Response record
# Error types and availability
(r'\bGenerationError\b', 4), # Generation error type
(r'\bToolCallError\b', 5), # Tool execution error
(r'\.exceededContextWindowSize\b', 5), # Context window error
(r'\.guardrailViolation\b', 5), # Safety guardrail error
(r'\.appleIntelligenceNotEnabled\b', 5), # Availability reason
(r'\.deviceNotEligible\b', 5), # Device eligibility
(r'\.modelNotReady\b', 5), # Model readiness
# Use cases and guardrails
(r'\.contentTagging\b', 5), # Content tagging use case
(r'\.permissiveContentTransformations\b', 5), # Guardrail setting
# Common usage patterns
(r'LanguageModelSession\s*\(\s*instructions:', 5), # Session init
(r'for\s+try\s+await.*streamResponse', 5), # Streaming iteration
(r'\.PartiallyGenerated\b', 5), # Partial generation type
],
}
def _validate_patterns(patterns: Dict[str, List[Tuple[str, int]]]) -> None:
"""
Validate pattern structure at module load time.
Ensures all patterns follow the expected format:
- Each pattern is a (regex_string, weight) tuple
- Weight is an integer between 1 and 5
- Regex string is a valid string
Raises:
ValueError: If any pattern is malformed
"""
for lang, pattern_list in patterns.items():
for i, item in enumerate(pattern_list):
if not isinstance(item, tuple) or len(item) != 2:
raise ValueError(
f"Pattern {i} for '{lang}' is not a (regex, weight) tuple: {item}"
)
pattern, weight = item
if not isinstance(pattern, str):
raise ValueError(
f"Pattern {i} for '{lang}': regex must be a string, got {type(pattern).__name__}"
)
if not isinstance(weight, int) or weight < 1 or weight > 5:
raise ValueError(
f"Pattern {i} for '{lang}': weight must be int 1-5, got {weight!r}"
)
# Validate patterns at module load time
try:
_validate_patterns(SWIFT_PATTERNS)
except ValueError as e:
logger.error(
"Swift pattern validation failed: %s. Swift detection will be disabled. "
"This indicates a bug in swift_patterns.py - please file an issue.",
e
)
# Clear patterns to prevent broken detection with invalid data
SWIFT_PATTERNS = {}