diff --git a/CHANGES.md b/CHANGES.md
index 7e58c3474f..39e6cd9b8e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -28,6 +28,7 @@ Language grammar improvements:
- Added support for quoted identifiers, implicit parameters, and property wrapper projections
- Support for more complex expressions in string interpolation
- enh(swift) Improved highlighting for types and generic arguments (#2920) [Steven Van Impe][]
+- enh(swift) Improved highlighting for functions, initializers, and subscripts (#2930) [Steven Van Impe][]
- fix(http) avoid recursive sublanguage and tighten rules (#2893) [Josh Goebel][]
- fix(asciidoc): Handle section titles level 5 (#2868) [Vaibhav Chanana][]
- fix(asciidoc): Support unconstrained emphasis syntax (#2869) [Guillaume Grossetie][]
diff --git a/src/languages/lib/kws_swift.js b/src/languages/lib/kws_swift.js
index 77b1021017..a52ca459bc 100644
--- a/src/languages/lib/kws_swift.js
+++ b/src/languages/lib/kws_swift.js
@@ -52,7 +52,7 @@ export const keywords = [
'enum',
'extension',
'fallthrough',
- 'fileprivate(set)',
+ /fileprivate\(set\)/,
'fileprivate',
'final', // contextual
'for',
@@ -66,7 +66,7 @@ export const keywords = [
/init\?/,
/init!/,
'inout',
- 'internal(set)',
+ /internal\(set\)/,
'internal',
'in',
'is', // operator
@@ -74,7 +74,7 @@ export const keywords = [
'let',
'mutating', // contextual
'nonmutating', // contextual
- 'open(set)', // contextual
+ /open\(set\)/, // contextual
'open', // contextual
'operator',
'optional', // contextual
@@ -82,10 +82,10 @@ export const keywords = [
'postfix', // contextual
'precedencegroup',
'prefix', // contextual
- 'private(set)',
+ /private\(set\)/,
'private',
'protocol',
- 'public(set)',
+ /public\(set\)/,
'public',
'repeat',
'required', // contextual
@@ -104,8 +104,8 @@ export const keywords = [
/try!/, // operator
'try', // operator
'typealias',
- 'unowned(safe)', // contextual
- 'unowned(unsafe)', // contextual
+ /unowned\(safe\)/, // contextual
+ /unowned\(unsafe\)/, // contextual
'unowned', // contextual
'var',
'weak', // contextual
@@ -240,7 +240,7 @@ export const identifierHead = either(
/[\u2C00-\u2DFF\u2E80-\u2FFF]/,
/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,
/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,
- /[\uFE47-\uFFFD]/
+ /[\uFE47-\uFEFE\uFF00-\uFFFD]/ // Should be /[\uFE47-\uFFFD]/, but we have to exclude FEFF.
// The following characters are also allowed, but the regexes aren't supported yet.
// /[\u{10000}-\u{1FFFD}\u{20000-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}]/u,
// /[\u{50000}-\u{5FFFD}\u{60000-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}]/u,
diff --git a/src/languages/swift.js b/src/languages/swift.js
index 3b95e40676..5f8e1a7389 100644
--- a/src/languages/swift.js
+++ b/src/languages/swift.js
@@ -16,6 +16,10 @@ import {
/** @type LanguageFn */
export default function(hljs) {
+ const WHITESPACE = {
+ match: /\s+/,
+ relevance: 0
+ };
// https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID411
const BLOCK_COMMENT = hljs.COMMENT(
'/\\*',
@@ -24,6 +28,10 @@ export default function(hljs) {
contains: [ 'self' ]
}
);
+ const COMMENTS = [
+ hljs.C_LINE_COMMENT_MODE,
+ BLOCK_COMMENT
+ ];
// https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413
// https://docs.swift.org/swift-book/ReferenceManual/zzSummaryOfTheGrammar.html
@@ -35,7 +43,7 @@ export default function(hljs) {
};
const KEYWORD_GUARD = {
// Consume .keyword to prevent highlighting properties and methods as keywords.
- begin: concat(/\./, either(...Swift.keywords)),
+ match: concat(/\./, either(...Swift.keywords)),
relevance: 0
};
const PLAIN_KEYWORDS = Swift.keywords
@@ -49,14 +57,14 @@ export default function(hljs) {
variants: [
{
className: 'keyword',
- begin: either(...REGEX_KEYWORDS, ...Swift.optionalDotKeywords)
+ match: either(...REGEX_KEYWORDS, ...Swift.optionalDotKeywords)
}
]
};
// find all the regular keywords
const KEYWORDS = {
$pattern: either(
- /\b\w+(\(\w+\))?/, // kw or kw(arg)
+ /\b\w+/, // regular keywords
/#\w+/ // number keywords
),
keyword: PLAIN_KEYWORDS
@@ -73,12 +81,12 @@ export default function(hljs) {
// https://github.com/apple/swift/tree/main/stdlib/public/core
const BUILT_IN_GUARD = {
// Consume .built_in to prevent highlighting properties and methods.
- begin: concat(/\./, either(...Swift.builtIns)),
+ match: concat(/\./, either(...Swift.builtIns)),
relevance: 0
};
const BUILT_IN = {
className: 'built_in',
- begin: concat(/\b/, either(...Swift.builtIns), /(?=\()/)
+ match: concat(/\b/, either(...Swift.builtIns), /(?=\()/)
};
const BUILT_INS = [
BUILT_IN_GUARD,
@@ -88,7 +96,7 @@ export default function(hljs) {
// https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418
const OPERATOR_GUARD = {
// Prevent -> from being highlighting as an operator.
- begin: /->/,
+ match: /->/,
relevance: 0
};
const OPERATOR = {
@@ -96,13 +104,13 @@ export default function(hljs) {
relevance: 0,
variants: [
{
- begin: Swift.operator
+ match: Swift.operator
},
{
// dot-operator: only operators that start with a dot are allowed to use dots as
// characters (..., ...<, .*, etc). So there rule here is: a dot followed by one or more
// characters that may also include dots.
- begin: `\\.(\\.|${Swift.operatorCharacter})+`
+ match: `\\.(\\.|${Swift.operatorCharacter})+`
}
]
};
@@ -121,19 +129,19 @@ export default function(hljs) {
variants: [
// decimal floating-point-literal (subsumes decimal-literal)
{
- begin: `\\b(${decimalDigits})(\\.(${decimalDigits}))?` + `([eE][+-]?(${decimalDigits}))?\\b`
+ match: `\\b(${decimalDigits})(\\.(${decimalDigits}))?` + `([eE][+-]?(${decimalDigits}))?\\b`
},
// hexadecimal floating-point-literal (subsumes hexadecimal-literal)
{
- begin: `\\b0x(${hexDigits})(\\.(${hexDigits}))?` + `([pP][+-]?(${decimalDigits}))?\\b`
+ match: `\\b0x(${hexDigits})(\\.(${hexDigits}))?` + `([pP][+-]?(${decimalDigits}))?\\b`
},
// octal-literal
{
- begin: /\b0o([0-7]_*)+\b/
+ match: /\b0o([0-7]_*)+\b/
},
// binary-literal
{
- begin: /\b0b([01]_*)+\b/
+ match: /\b0b([01]_*)+\b/
}
]
};
@@ -143,16 +151,16 @@ export default function(hljs) {
className: 'subst',
variants: [
{
- begin: concat(/\\/, rawDelimiter, /[0\\tnr"']/)
+ match: concat(/\\/, rawDelimiter, /[0\\tnr"']/)
},
{
- begin: concat(/\\/, rawDelimiter, /u\{[0-9a-fA-F]{1,8}\}/)
+ match: concat(/\\/, rawDelimiter, /u\{[0-9a-fA-F]{1,8}\}/)
}
]
});
const ESCAPED_NEWLINE = (rawDelimiter = "") => ({
className: 'subst',
- begin: concat(/\\/, rawDelimiter, /[\t ]*(?:[\r\n]|\r\n)/)
+ match: concat(/\\/, rawDelimiter, /[\t ]*(?:[\r\n]|\r\n)/)
});
const INTERPOLATION = (rawDelimiter = "") => ({
className: 'subst',
@@ -193,15 +201,15 @@ export default function(hljs) {
// https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID412
const QUOTED_IDENTIFIER = {
- begin: concat(/`/, Swift.identifier, /`/)
+ match: concat(/`/, Swift.identifier, /`/)
};
const IMPLICIT_PARAMETER = {
className: 'variable',
- begin: /\$\d+/
+ match: /\$\d+/
};
const PROPERTY_WRAPPER_PROJECTION = {
className: 'variable',
- begin: `\\$${Swift.identifierCharacter}+`
+ match: `\\$${Swift.identifierCharacter}+`
};
const IDENTIFIERS = [
QUOTED_IDENTIFIER,
@@ -211,30 +219,30 @@ export default function(hljs) {
// https://docs.swift.org/swift-book/ReferenceManual/Attributes.html
const AVAILABLE_ATTRIBUTE = {
- begin: /(@|#)available\(/,
- end: /\)/,
- keywords: {
- $pattern: /[@#]?\w+/,
- keyword: Swift.availabilityKeywords
- .concat([
- "@available",
- "#available"
- ])
- .join(' ')
- },
- contains: [
- ...OPERATORS,
- NUMBER,
- STRING
- ]
+ match: /(@|#)available/,
+ className: "keyword",
+ starts: {
+ contains: [
+ {
+ begin: /\(/,
+ end: /\)/,
+ keywords: Swift.availabilityKeywords.join(' '),
+ contains: [
+ ...OPERATORS,
+ NUMBER,
+ STRING
+ ]
+ }
+ ]
+ }
};
const KEYWORD_ATTRIBUTE = {
className: 'keyword',
- begin: concat(/@/, either(...Swift.keywordAttributes))
+ match: concat(/@/, either(...Swift.keywordAttributes))
};
const USER_DEFINED_ATTRIBUTE = {
className: 'meta',
- begin: concat(/@/, Swift.identifier)
+ match: concat(/@/, Swift.identifier)
};
const ATTRIBUTES = [
AVAILABLE_ATTRIBUTE,
@@ -244,28 +252,28 @@ export default function(hljs) {
// https://docs.swift.org/swift-book/ReferenceManual/Types.html
const TYPE = {
- begin: lookahead(/\b[A-Z]/),
+ match: lookahead(/\b[A-Z]/),
relevance: 0,
contains: [
{ // Common Apple frameworks, for relevance boost
className: 'type',
- begin: concat(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/, Swift.identifierCharacter, '+')
+ match: concat(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/, Swift.identifierCharacter, '+')
},
{ // Type identifier
className: 'type',
- begin: Swift.typeIdentifier,
+ match: Swift.typeIdentifier,
relevance: 0
},
{ // Optional type
- begin: /[?!]+/,
+ match: /[?!]+/,
relevance: 0
},
{ // Variadic parameter
- begin: /\.\.\./,
+ match: /\.\.\./,
relevance: 0
},
{ // Protocol composition
- begin: concat(/\s+&\s+/, lookahead(Swift.typeIdentifier)),
+ match: concat(/\s+&\s+/, lookahead(Swift.typeIdentifier)),
relevance: 0
}
]
@@ -275,6 +283,7 @@ export default function(hljs) {
end: />/,
keywords: KEYWORDS,
contains: [
+ ...COMMENTS,
...KEYWORD_MODES,
...ATTRIBUTES,
OPERATOR_GUARD,
@@ -283,6 +292,128 @@ export default function(hljs) {
};
TYPE.contains.push(GENERIC_ARGUMENTS);
+ // https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#ID552
+ // Prevents element names from being highlighted as keywords.
+ const TUPLE_ELEMENT_NAME = {
+ match: concat(Swift.identifier, /\s*:/),
+ keywords: "_|0",
+ relevance: 0
+ };
+ // Matches tuples as well as the parameter list of a function type.
+ const TUPLE = {
+ begin: /\(/,
+ end: /\)/,
+ relevance: 0,
+ keywords: KEYWORDS,
+ contains: [
+ 'self',
+ TUPLE_ELEMENT_NAME,
+ ...COMMENTS,
+ ...KEYWORD_MODES,
+ ...BUILT_INS,
+ ...OPERATORS,
+ NUMBER,
+ STRING,
+ ...IDENTIFIERS,
+ ...ATTRIBUTES,
+ TYPE
+ ]
+ };
+
+ // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID362
+ // Matches both the keyword func and the function title.
+ // Grouping these lets us differentiate between the operator function <
+ // and the start of the generic parameter clause (also <).
+ const FUNC_PLUS_TITLE = {
+ beginKeywords: 'func',
+ contains: [
+ {
+ className: 'title',
+ match: either(QUOTED_IDENTIFIER.match, Swift.identifier, Swift.operator),
+ // Required to make sure the opening < of the generic parameter clause
+ // isn't parsed as a second title.
+ endsParent: true,
+ relevance: 0
+ },
+ WHITESPACE
+ ]
+ };
+ const GENERIC_PARAMETERS = {
+ begin: /,
+ end: />/,
+ contains: [
+ ...COMMENTS,
+ TYPE
+ ]
+ };
+ const FUNCTION_PARAMETER_NAME = {
+ begin: either(
+ lookahead(concat(Swift.identifier, /\s*:/)),
+ lookahead(concat(Swift.identifier, /\s+/, Swift.identifier, /\s*:/))
+ ),
+ end: /:/,
+ relevance: 0,
+ contains: [
+ {
+ className: 'keyword',
+ match: /\b_\b/
+ },
+ {
+ className: 'params',
+ match: Swift.identifier
+ }
+ ]
+ };
+ const FUNCTION_PARAMETERS = {
+ begin: /\(/,
+ end: /\)/,
+ keywords: KEYWORDS,
+ contains: [
+ FUNCTION_PARAMETER_NAME,
+ ...COMMENTS,
+ ...KEYWORD_MODES,
+ ...OPERATORS,
+ NUMBER,
+ STRING,
+ ...ATTRIBUTES,
+ TYPE,
+ TUPLE
+ ],
+ endsParent: true,
+ illegal: /["']/
+ };
+ const FUNCTION = {
+ className: 'function',
+ match: lookahead(/\bfunc\b/),
+ contains: [
+ FUNC_PLUS_TITLE,
+ GENERIC_PARAMETERS,
+ FUNCTION_PARAMETERS,
+ WHITESPACE
+ ],
+ illegal: [
+ /\[/,
+ /%/
+ ]
+ };
+
+ // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID375
+ // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID379
+ const INIT_SUBSCRIPT = {
+ className: 'function',
+ match: /\b(subscript|init[?!]?)\s*(?=[<(])/,
+ keywords: {
+ keyword: "subscript init init? init!",
+ $pattern: /\w+[?!]?/
+ },
+ contains: [
+ GENERIC_PARAMETERS,
+ FUNCTION_PARAMETERS,
+ WHITESPACE
+ ],
+ illegal: /\[|%/
+ };
+
// Add supported submodes to string interpolation.
for (const variant of STRING.variants) {
const interpolation = variant.contains.find(mode => mode.label === "interpol");
@@ -313,42 +444,9 @@ export default function(hljs) {
name: 'Swift',
keywords: KEYWORDS,
contains: [
- hljs.C_LINE_COMMENT_MODE,
- BLOCK_COMMENT,
- {
- className: 'function',
- beginKeywords: 'func',
- end: /\{/,
- excludeEnd: true,
- contains: [
- hljs.inherit(hljs.TITLE_MODE, {
- begin: /[A-Za-z$_][0-9A-Za-z$_]*/
- }),
- {
- begin: /,
- end: />/
- },
- {
- className: 'params',
- begin: /\(/,
- end: /\)/,
- endsParent: true,
- keywords: KEYWORDS,
- contains: [
- 'self',
- ...KEYWORD_MODES,
- NUMBER,
- STRING,
- hljs.C_BLOCK_COMMENT_MODE,
- { // relevance booster
- begin: ':'
- }
- ],
- illegal: /["']/
- }
- ],
- illegal: /\[|%/
- },
+ ...COMMENTS,
+ FUNCTION,
+ INIT_SUBSCRIPT,
{
className: 'class',
beginKeywords: 'struct protocol class extension enum',
@@ -365,10 +463,7 @@ export default function(hljs) {
{
beginKeywords: 'import',
end: /$/,
- contains: [
- hljs.C_LINE_COMMENT_MODE,
- BLOCK_COMMENT
- ],
+ contains: [ ...COMMENTS ],
relevance: 0
},
...KEYWORD_MODES,
@@ -378,7 +473,8 @@ export default function(hljs) {
STRING,
...IDENTIFIERS,
...ATTRIBUTES,
- TYPE
+ TYPE,
+ TUPLE
]
};
}
diff --git a/test/markup/swift/functions.expect.txt b/test/markup/swift/functions.expect.txt
index a83afdfb6b..0a10c71ff0 100644
--- a/test/markup/swift/functions.expect.txt
+++ b/test/markup/swift/functions.expect.txt
@@ -1,10 +1,32 @@
-protocol Protocol {
- func f1()
- func f2()
-}
+func f1<
+ X,
+ Y: A,
+
+ Z: B & C<D>
+>() where X == Y, Y: A, Z: B & C<D> { }
+
+func < <T>() { }
+
+func f2(_ p: @escaping () throws -> Void) rethrows -> some Collection { }
+
+func f3(
+ p1e p1i: inout Int = 5,
+ _ p2: (x: Int, y: Int),
+ p3: (var: Int, let: Int) throws -> Int,
+ p4: Int...
+ p5: @attribute String? = "text"
+) { }
+
+init<X: A>(_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+init?(_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+init! (_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+
+subscript<X: A>(_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+
+protocol Comparable: Equatable {
-class MyClass {
- func f() {
- return true
- }
+ static func < (lhs: Self, rhs: Self) -> Bool
+ static func <= (lhs: Self, rhs: Self) -> Bool
+ static func > (lhs: Self, rhs: Self) -> Bool
+ static func >= (lhs: Self, rhs: Self) -> Bool
}
diff --git a/test/markup/swift/functions.txt b/test/markup/swift/functions.txt
index cfd64aed92..1f8807d3c2 100644
--- a/test/markup/swift/functions.txt
+++ b/test/markup/swift/functions.txt
@@ -1,10 +1,32 @@
-protocol Protocol {
- func f1()
- func f2()
-}
+func f1<
+ X,
+ Y: A,
+ // documentation
+ Z: B & C
+>() where X == Y, Y: A, Z: B & C { }
+
+func < () { }
+
+func f2(_ p: @escaping () throws -> Void) rethrows -> some Collection { }
+
+func f3(
+ p1e p1i: inout Int = 5,
+ _ p2: (x: Int, y: Int),
+ p3: (var: Int, let: Int) throws -> Int,
+ p4: Int...
+ p5: @attribute String? = "text"
+) { }
+
+init(_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+init?(_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+init! (_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+
+subscript(_ p: @attribute inout (x: Int, var: Int) = (0, 0)) { }
+
+protocol Comparable: Equatable {
-class MyClass {
- func f() {
- return true
- }
+ static func < (lhs: Self, rhs: Self) -> Bool
+ static func <= (lhs: Self, rhs: Self) -> Bool
+ static func > (lhs: Self, rhs: Self) -> Bool
+ static func >= (lhs: Self, rhs: Self) -> Bool
}
diff --git a/test/markup/swift/keywords.expect.txt b/test/markup/swift/keywords.expect.txt
index a7f03bca5e..73adf593a4 100644
--- a/test/markup/swift/keywords.expect.txt
+++ b/test/markup/swift/keywords.expect.txt
@@ -10,7 +10,7 @@ x as Int
x as? Double
x as! String
x is String
-init?() init!() init
+init? init! init
try? try! try
true false nil
fileprivate(set) internal(set) open(set) private(set) public(set)
diff --git a/test/markup/swift/keywords.txt b/test/markup/swift/keywords.txt
index 6b354c0db1..53358810d9 100644
--- a/test/markup/swift/keywords.txt
+++ b/test/markup/swift/keywords.txt
@@ -10,7 +10,7 @@ x as Int
x as? Double
x as! String
x is String
-init?() init!() init
+init? init! init
try? try! try
true false nil
fileprivate(set) internal(set) open(set) private(set) public(set)
diff --git a/test/markup/swift/tuples.expect.txt b/test/markup/swift/tuples.expect.txt
new file mode 100644
index 0000000000..6b259a3343
--- /dev/null
+++ b/test/markup/swift/tuples.expect.txt
@@ -0,0 +1,16 @@
+(3, "string")
+(c: (x: 1, y: 1), z: 1)
+(var: Array<Int>, let: Array<Double>)
+(_ x: inout Int) throws -> Int
+(abs(-2), abs(2))
+(x < y, a > b)
+($0, $1)
+(@escaping (String) -> Void, @autoclosure () -> String) -> String
+(
+
+ x,
+
+ y
+)
+(let x, var y)
+([key: value, key: value])
diff --git a/test/markup/swift/tuples.txt b/test/markup/swift/tuples.txt
new file mode 100644
index 0000000000..c66e4bd33a
--- /dev/null
+++ b/test/markup/swift/tuples.txt
@@ -0,0 +1,16 @@
+(3, "string")
+(c: (x: 1, y: 1), z: 1)
+(var: Array, let: Array)
+(_ x: inout Int) throws -> Int
+(abs(-2), abs(2))
+(x < y, a > b)
+($0, $1)
+(@escaping (String) -> Void, @autoclosure () -> String) -> String
+(
+ // x
+ x,
+ /* y */
+ y
+)
+(let x, var y)
+([key: value, key: value])
diff --git a/test/markup/swift/types.expect.txt b/test/markup/swift/types.expect.txt
index 731db7c394..4bd3695140 100644
--- a/test/markup/swift/types.expect.txt
+++ b/test/markup/swift/types.expect.txt
@@ -41,3 +41,7 @@
Dictionary<String, Any>
Dictionary<String, Array<Int>>
Array<(@autoclosure () -> String) throws -> String?>
+Array<
+
+ Int
+>
diff --git a/test/markup/swift/types.txt b/test/markup/swift/types.txt
index 0348b00e9a..89a27ea6a9 100644
--- a/test/markup/swift/types.txt
+++ b/test/markup/swift/types.txt
@@ -41,3 +41,7 @@ Array
Dictionary
Dictionary>
Array<(@autoclosure () -> String) throws -> String?>
+Array<
+ // documentation
+ Int
+>