This package implements Swift (.swift) language support for the CodeMirror code editor: a full Lezer grammar covering classes, structs, enums, protocols, extensions, actors, functions (including async/throws), closures (including trailing closure syntax), optionals, generics, pattern matching, string interpolation, and documentation comments — with syntax highlighting compatible with any CodeMirror 6 theme.
This code is released under an MIT license.
import { EditorView, basicSetup } from "codemirror"
import { swift } from "@fazelstudio/codemirror-lang-swift"
new EditorView({
parent: document.body,
doc: `struct Greeting {
let name: String
var message: String {
"Hello, \(name)!"
}
}`,
extensions: [basicSetup, swift()],
})Create a Swift language support extension. No configuration is required.
import { swift } from "@fazelstudio/codemirror-lang-swift"
EditorView.create({ extensions: [swift()] })The underlying LRLanguage instance. Useful for custom configuration, e.g. adding extra language data or completions:
import { swiftLanguage } from "@fazelstudio/codemirror-lang-swift"
swiftLanguage.data.of({ autocomplete: myCompletions })- Declarations:
class,struct,enum(associated & raw values),protocol,extension,actor,typealias,associatedtype,init/init?/init!,subscript,operator/precedencegroup - Generics: inline constraints (
<T: Comparable>) andwhereclause (where T: Equatable) - Properties: stored, computed (getter-only &
get/set),willSet/didSet, property wrappers (@State,@Published, etc.) - Functions: external/internal parameter labels (
for id),_label,inout, default values,async/throws/rethrows,mutating/static/class/overrideetc. - Statements:
if let/guard let(multiple bindings),switch/case(value, tuple, bindinglet x?,where, range, multiple values,default,fallthrough),for ... where,while,repeat-while,defer,do/catch,throw/try/try?/try!/await, labeledbreak/continue - Expressions: optional chaining
a?.b, force unwrapa!, nil-coalescinga ?? b, range.../..<, castingas/as?/as!/is, ternary? :, trailing closures (single & multipleonError:), shorthand$0, string interpolation (simple & nested), multi-line"""and raw#"..."#strings - Comments:
//, nested/* */(/* /* */ */valid), doc comments///and/** */ - Attributes:
@available(...),@objc,@escaping,@State,@MainActor, etc. - Operators: custom
infix operator +-: AdditionPrecedenceand overloadingstatic func +(lhs:rhs:)
- Custom
precedencegroupdeclarations are parsed structurally but do not affect how this package parses operator precedence for custom operators — all custom operators are treated with a default precedence level in v0.2. - Result builder DSL bodies (e.g. SwiftUI
@ViewBuilderclosures) are parsed as regular Swift statements, not with builder-specific semantics — this only affects semantic understanding, not syntax highlighting correctness. - Doc comment field content (
- Parameter x: ...) is highlighted as part of the doc comment block, not parsed into structured fields, in v0.2. - String interpolation
\(expr)inside regular strings is tokenized as part of the string token in v0.2 (interpolation delimiters are not separately highlighted). For raw strings with\#(…)and multi-line"""with interpolation, the content inside\(…)is still part of the surrounding string token, not a separate expression tree — this is intentional to avoid LR conflicts with()depth and"handling, and will be addressed via external tokenizer in a future 0.3 if needed. Extended delimiter with arbitrary#count (##"..."##,###"..."###etc) is now supported via#+regex (previously limited to 2), but interpolation\##(...for >1#is still not delimited separately. return/break/continue/throwwith trailing;are now correctly parsed as single statements (ReturnStatement:309kw<"return"> !label Expression ";"?,BreakStatement/ContinueStatement!label Identifier ";"?,ThrowStatementExpression ";"?— fixesreturn;/break outer;previously⚠(";")), andreturn <value>/break <label>are single statements via!labelprecedence (fixesreturn 42previously split intoreturn+42as 2 statements viaExpressionStatement). Remaining LR(1) edge withouttrackNewlineisreturnwithout;+ newline vs nextlet— correctly not greedy becauseletis notExpression, soreturnalone +let x=1stays 2 statements.- Trailing closure vs
if/while/for/guardblock ambiguity remains forif x.isEmpty { }style wherex.isEmptycould be interpreted asx.isEmptywith trailing closure{ }as part of condition (ConditionincludesExpressionwith trailing) rather thanifblock — handled via!typeArgsvs!callprecedence andCodeBlock[@dynamicPrecedence=1] ~call, but still requires()around condition if it contains a call with trailing closure to be unambiguous (documented in DECISIONS.md). for wherewith trailing closure on thewherecondition (for x in arr where foo() { } { }) andif letwith trailing on the bound value (if let x = foo() { } { }) remain ambiguous and will be parsed asfoo() { }trailing inside thewhere/letvalue, not as loop/if body — use parentheseswhere (foo() { })or extract to variable to disambiguate.- Generic
<...>vs comparisona < bis handled via~typeArgs+@dynamicPrecedence=1onGenericTypeandTypevsExpressiondistinction, butlet x: Array<Dictionary<String, Int>>nested andBox<Int>(value:)asPrimaryExpressionare now supported; remaining edgea<B, C>as value still prefers generic when after:oras, and comparison otherwise — this matches Swift compiler heuristics (no space before<after identifier in type context). some/anyopaque types (some View,any Protocol) are now supported asOpaqueTypewitht.modifierhighlighting, butsome/anyas generic constraints (where T: some Protocol) are still parsed asIdentifier+Type, not as opaque.
npm install
npm run build:grammar # swift.grammar -> src/parser.js
npm run build # src/*.ts -> dist/*.js + .cjs + .d.ts
npm test # mocha test/test-swift.js (43 tests)MIT © Zulfazli (Fazelllyyy) — fazel-studio