From 60a32b2c1730610b0aad3bc5447c76f2e2ae9d3c Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Fri, 4 Dec 2020 21:54:38 +0100 Subject: [PATCH 01/17] (swift) Revamped keywords and added support for operators. Also fixes #2856. --- src/languages/swift.js | 134 ++++++++++++++---- test/markup/swift/multiline-string.expect.txt | 2 +- test/markup/swift/numbers.expect.txt | 10 +- test/markup/swift/operators.expect.txt | 109 ++++++++++++++ test/markup/swift/operators.txt | 109 ++++++++++++++ 5 files changed, 327 insertions(+), 37 deletions(-) create mode 100644 test/markup/swift/operators.expect.txt create mode 100644 test/markup/swift/operators.txt diff --git a/src/languages/swift.js b/src/languages/swift.js index 1b829584ee..913934d76e 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -7,39 +7,52 @@ Website: https://swift.org Category: common, system */ +import { either } from "../lib/regex"; export default function(hljs) { - var SWIFT_KEYWORDS = { - // override the pattern since the default of of /\w+/ is not sufficient to - // capture the keywords that start with the character "#" - $pattern: /[\w#]+/, - keyword: '#available #colorLiteral #column #else #elseif #endif #file ' + - '#fileLiteral #function #if #imageLiteral #line #selector #sourceLocation ' + - '_ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype ' + - 'associativity break case catch class continue convenience default defer deinit didSet do ' + - 'dynamic dynamicType else enum extension fallthrough false fileprivate final for func ' + - 'get guard if import in indirect infix init inout internal is lazy left let ' + - 'mutating nil none nonmutating open operator optional override postfix precedence ' + - 'prefix private protocol Protocol public repeat required rethrows return ' + - 'right self Self set some static struct subscript super switch throw throws true ' + - 'try try! try? Type typealias unowned var weak where while willSet', - literal: 'true false nil', - built_in: 'abs advance alignof alignofValue anyGenerator assert assertionFailure ' + - 'bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC ' + - 'bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros ' + - 'debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords ' + - 'enumerate equal fatalError filter find getBridgedObjectiveCType getVaList ' + - 'indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC ' + - 'isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare ' + - 'map max maxElement min minElement numericCast overlaps partition posix ' + - 'precondition preconditionFailure print println quickSort readLine reduce reflect ' + - 'reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split ' + - 'startsWith stride strideof strideofValue swap toString transcode ' + - 'underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap ' + - 'unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer ' + - 'withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers ' + - 'withUnsafePointer withUnsafePointers withVaList zip' - }; + // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413 + // https://docs.swift.org/swift-book/ReferenceManual/zzSummaryOfTheGrammar.html + const SWIFT_KEYWORDS = { + // Override the pattern since the default of /\w+/ is not sufficient to capture + // the keywords that start with a number sign (#) or that have parentheses. + $pattern: /#?\w+(\(\w+\))?/, + keyword: + // Reserved keywords. These require backticks to use as an identifier: + '_ Any as associatedtype break case catch class continue default defer deinit do else enum extension ' + + 'fallthrough fileprivate fileprivate(set) for func guard if import in init inout internal internal(set) is ' + + 'let operator precedencegroup private private(set) protocol public public(set) repeat rethrows return ' + + 'self Self static struct subscript super switch throw throws try typealias var where while ' + + // This list includes as, is, and try, even though they're technically operators. + // The remaining operators as?, as!, try?, and try! are declared in the OPERATOR mode. + + // Keywords that begin with a number sign: + '#available #colorLiteral #column #dsohandle #else #elseif #endif #error #file #fileID #fileLiteral #filePath ' + + '#function #if #imageLiteral #keyPath #line #selector #sourceLocation #warning ' + + + // Keywords reserved in particular contexts. These can be used as identifiers: + 'convenience didSet dynamic final get infix indirect lazy mutating nonmutating open open(set) ' + + 'optional override postfix prefix Protocol required set some Type unowned unowned(safe) unowned(unsafe) weak willSet ', + // Ideally, these keywords should only be matched in specific modes, as they can result in false positives. + // For example, the following keywords are commented out because they're only used in + // precedencegroup declarations, so they will likely result in mostly false positives: + // 'assignment associativity higherThan left lowerThan none right ' + literal: 'true false nil', + built_in: 'abs advance alignof alignofValue anyGenerator assert assertionFailure ' + + 'bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC ' + + 'bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros ' + + 'debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords ' + + 'enumerate equal fatalError filter find getBridgedObjectiveCType getVaList ' + + 'indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC ' + + 'isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare ' + + 'map max maxElement min minElement numericCast overlaps partition posix ' + + 'precondition preconditionFailure print println quickSort readLine reduce reflect ' + + 'reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split ' + + 'startsWith stride strideof strideofValue swap toString transcode ' + + 'underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap ' + + 'unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer ' + + 'withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers ' + + 'withUnsafePointer withUnsafePointers withVaList zip' + }; var TYPE = { className: 'type', @@ -98,6 +111,64 @@ export default function(hljs) { }; SUBST.contains = [NUMBER]; + // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418 + const operatorHead = either( + /[/=\-+!*%<>&|^~?]/, + /[\u00A1–\u00A7]/, + /[\u00A9\u00AB]/, + /[\u00AC\u00AE]/, + /[\u00B0\u00B1]/, + /[\u00B6\u00BB\u00BF\u00D7\u00F7]/, + /[\u2016–\u2017]/, + /[\u2020–\u2027]/, + /[\u2030–\u203E]/, + /[\u2041–\u2053]/, + /[\u2055–\u205E]/, + /[\u2190–\u23FF]/, + /[\u2500–\u2775]/, + /[\u2794–\u2BFF]/, + /[\u2E00–\u2E7F]/, + /[\u3001–\u3003]/, + /[\u3008–\u3020]/, + /[\u3030]/ + ); + const operatorCharacter = either( + operatorHead, + /[\u0300–\u036F]/, + /[\u1DC0–\u1DFF]/, + /[\u20D0–\u20FF]/, + /[\uFE00–\uFE0F]/, + /[\uFE20–\uFE2F]/, + // TODO: The following characters are also allowed, but the regex doesn't work as intended. + // For example, it also matches -10 as an operator. + // /[\u{E0100}–\u{E01EF}]/u + ); + const OPERATOR = { + className: 'operator', + variants: [ + { // TODO: Replace with negative look-behind when available. + className: 'keyword', + begin: /\s(?=as[?!]?\s)/, + excludeBegin: true, + end: /as[?!]?(?=\s)/ + }, + { // TODO: Replace with negative look-behind when available. + className: 'keyword', + begin: /\s(?=is\s)/, + excludeBegin: true, + end: /is(?=\s)/ + }, + { // TODO: Replace with negative look-behind when available. + className: 'keyword', + begin: /[^.](?=\btry[?!]?\s)/, + excludeBegin: true, + end: /try[?!]?(?=\s)/ + }, + { begin: `${operatorHead}${operatorCharacter}*` }, + { begin: `\\.(\\.|${operatorCharacter})+` } + ] + }; + return { name: 'Swift', keywords: SWIFT_KEYWORDS, @@ -105,6 +176,7 @@ export default function(hljs) { STRING, hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT, + OPERATOR, OPTIONAL_USING_TYPE, TYPE, NUMBER, diff --git a/test/markup/swift/multiline-string.expect.txt b/test/markup/swift/multiline-string.expect.txt index 67880ae8cf..390cb4da0e 100644 --- a/test/markup/swift/multiline-string.expect.txt +++ b/test/markup/swift/multiline-string.expect.txt @@ -1,3 +1,3 @@ -var string = """ +var string = """ var a = not actually code """ \ No newline at end of file diff --git a/test/markup/swift/numbers.expect.txt b/test/markup/swift/numbers.expect.txt index 95eb9a2c2b..d022e50f9c 100644 --- a/test/markup/swift/numbers.expect.txt +++ b/test/markup/swift/numbers.expect.txt @@ -22,11 +22,11 @@ // expressions containing numeric literals -+0 -+-1 -2-3 --10.magnitude -fn(-5) ++0 ++-1 +2-3 +-10.magnitude +fn(-5) 0x2.p2 // expressions not containing numeric literals diff --git a/test/markup/swift/operators.expect.txt b/test/markup/swift/operators.expect.txt new file mode 100644 index 0000000000..817d2b84c4 --- /dev/null +++ b/test/markup/swift/operators.expect.txt @@ -0,0 +1,109 @@ +x is y +x.is(y) +x as y +x as? y +x as! y +a.as(y) +try x +try? x +try! x +x.try(y) + +!true +~x ++1 +-1 +..<1 +...1 +0... +a? +a! + +a << 1 +a<<1 +a >> 1 +a>>1 +a * 1 +a*1 +a / 1 +a/1 +a % 1 +a%1 +a &* 1 +a&*1 +a & b +a&b +a + 1 +a+1 +a - 1 +a-1 +a &+ 1 +a&+1 +a &- 1 +a&-1 +a | b +a|b +a ^ b +a^b +0 ..< 1 +0..<1 +0 ... 1 +0...1 +a ?? 1 +a??1 +a < 1 +a<1 +a <= 1 +a<=1 +a > 1 +a>1 +a >= 1 +a>=1 +a == 1 +a==1 +a != 1 +a!=1 +a === b +a===b +a !== b +a!==b +a ~= 1 +a~=1 +a .== b +a.==b +a .!= b +a.!=b +a .< b +a.<b +a .<= b +a.<=b +a .> b +a.>b +a .>= b +a.>=b +true && false +true&&false +true || false +true||false +a = 1 +a=1 +a *= 1 +a*=1 +a /= 1 +a/=1 +a %= 1 +a%=1 +a += 1 +a+=1 +a -= 1 +a-=1 +a <<= 1 +a<<=1 +a >>= 1 +a>>=1 +a &= b +a&=b +a |= b +a|=b +a ^= b +a^=b diff --git a/test/markup/swift/operators.txt b/test/markup/swift/operators.txt new file mode 100644 index 0000000000..17d484c4f2 --- /dev/null +++ b/test/markup/swift/operators.txt @@ -0,0 +1,109 @@ +x is y +x.is(y) +x as y +x as? y +x as! y +a.as(y) +try x +try? x +try! x +x.try(y) + +!true +~x ++1 +-1 +..<1 +...1 +0... +a? +a! + +a << 1 +a<<1 +a >> 1 +a>>1 +a * 1 +a*1 +a / 1 +a/1 +a % 1 +a%1 +a &* 1 +a&*1 +a & b +a&b +a + 1 +a+1 +a - 1 +a-1 +a &+ 1 +a&+1 +a &- 1 +a&-1 +a | b +a|b +a ^ b +a^b +0 ..< 1 +0..<1 +0 ... 1 +0...1 +a ?? 1 +a??1 +a < 1 +a<1 +a <= 1 +a<=1 +a > 1 +a>1 +a >= 1 +a>=1 +a == 1 +a==1 +a != 1 +a!=1 +a === b +a===b +a !== b +a!==b +a ~= 1 +a~=1 +a .== b +a.==b +a .!= b +a.!=b +a .< b +a. b +a.>b +a .>= b +a.>=b +true && false +true&&false +true || false +true||false +a = 1 +a=1 +a *= 1 +a*=1 +a /= 1 +a/=1 +a %= 1 +a%=1 +a += 1 +a+=1 +a -= 1 +a-=1 +a <<= 1 +a<<=1 +a >>= 1 +a>>=1 +a &= b +a&=b +a |= b +a|=b +a ^= b +a^=b From 5d1e17cb1d82f651faee28683ca4dd35cc2190f0 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Fri, 4 Dec 2020 22:06:27 +0100 Subject: [PATCH 02/17] Update changelog. --- AUTHORS.txt | 1 + CHANGES.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 68d7233d31..571006e6b3 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -310,3 +310,4 @@ Contributors: - Kyle Brown - Marcus Ortiz - Guillaume Grossetie +- Steven Van Impe diff --git a/CHANGES.md b/CHANGES.md index ab26cb3d2a..81eccbf26c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ New Languages: Language improvements: - enh(makefile): Add `make` as an alias (#2883) [tripleee][] +- enh(swift) Revamped keywords and added support for operators (#2908) [Steven Van Impe][] Grammar improvements: @@ -24,7 +25,7 @@ Grammar improvements: [Oldes Huhuman]: https://github.com/Oldes [Josh Goebel]: https://github.com/joshgoebel [tripleee]: https://github.com/tripleee - +[Steven Van Impe]: https://github.com/svanimpe/ ## Version 10.4.0 From 4adf9ed08ae923a83784e8b3feabea3f93efc596 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Sat, 5 Dec 2020 11:46:36 +0100 Subject: [PATCH 03/17] (swift) Small fix for when the input starts with a try!? operator. --- src/languages/swift.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/swift.js b/src/languages/swift.js index 913934d76e..73ab504ccd 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -161,6 +161,7 @@ export default function(hljs) { { // TODO: Replace with negative look-behind when available. className: 'keyword', begin: /[^.](?=\btry[?!]?\s)/, + begin: /(^|[^.])(?=\btry[?!]?\s)/, excludeBegin: true, end: /try[?!]?(?=\s)/ }, From 2bb4994e2f8dacf4af4ed8383e7f76860a00a5a5 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Sat, 5 Dec 2020 12:59:47 +0100 Subject: [PATCH 04/17] (swift) Small fix for when the input starts with a try!? operator (part 2). --- src/languages/swift.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/languages/swift.js b/src/languages/swift.js index 73ab504ccd..ee18495ad7 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -160,7 +160,6 @@ export default function(hljs) { }, { // TODO: Replace with negative look-behind when available. className: 'keyword', - begin: /[^.](?=\btry[?!]?\s)/, begin: /(^|[^.])(?=\btry[?!]?\s)/, excludeBegin: true, end: /try[?!]?(?=\s)/ From 86b7c81d8f72bb9e3c9a775515a291be2670c337 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Thu, 10 Dec 2020 18:49:14 +0100 Subject: [PATCH 05/17] (swift) Big grammar improvements. --- src/languages/lib/swift.js | 285 +++++++++++++++++++++++ src/languages/swift.js | 273 +++++++++++----------- test/markup/swift/attributes.expect.txt | 9 + test/markup/swift/attributes.txt | 9 + test/markup/swift/functions.expect.txt | 2 +- test/markup/swift/identifiers.expect.txt | 3 + test/markup/swift/identifiers.txt | 3 + test/markup/swift/keywords.expect.txt | 25 ++ test/markup/swift/keywords.txt | 25 ++ test/markup/swift/operators.expect.txt | 21 +- test/markup/swift/operators.txt | 11 - test/markup/swift/strings.expect.txt | 12 +- test/markup/swift/strings.txt | 12 +- test/markup/swift/swiftui.expect.txt | 2 +- 14 files changed, 517 insertions(+), 175 deletions(-) create mode 100644 src/languages/lib/swift.js create mode 100644 test/markup/swift/attributes.expect.txt create mode 100644 test/markup/swift/attributes.txt create mode 100644 test/markup/swift/identifiers.expect.txt create mode 100644 test/markup/swift/identifiers.txt create mode 100644 test/markup/swift/keywords.expect.txt create mode 100644 test/markup/swift/keywords.txt diff --git a/src/languages/lib/swift.js b/src/languages/lib/swift.js new file mode 100644 index 0000000000..6c04d552de --- /dev/null +++ b/src/languages/lib/swift.js @@ -0,0 +1,285 @@ +import { concat, either } from '../../lib/regex'; + +// Keywords that require a leading dot. +export const dotKeywords = [ + /Protocol\b/, // contextual + /Type\b/, // contextual +]; + +// Keywords that may have a leading dot. +export const optionalDotKeywords = [ + /init\b/, + /self\b/ +]; + +// Regular keywords and literals. +export const keywords = [ + /\bAny\b/, + /\bSelf\b/, + /\b_\b/, // pattern + /\bassociatedtype\b/, + /\bas\?\B/, // operator + /\bas!\B/, // operator + /\bas\b/, // operator + /\bbreak\b/, + /\bcase\b/, + /\bcatch\b/, + /\bclass\b/, + /\bcontinue\b/, + /\bconvenience\b/, // contextual + /\bdefault\b/, + /\bdefer\b/, + /\bdeinit\b/, + /\bdidSet\b/, // contextual + /\bdo\b/, + /\bdynamic\b/, // contextual + /\belse\b/, + /\benum\b/, + /\bextension\b/, + /\bfallthrough\b/, + /\bfalse\b/, // literal + /\bfileprivate\(set\)\B/, + /\bfileprivate\b/, + /\bfinal\b/, // contextual + /\bfor\b/, + /\bfunc\b/, + /\bget\b/, // contextual + /\bguard\b/, + /\bif\b/, + /\bimport\b/, + /\bindirect\b/, // contextual + /\binfix\b/, // contextual + /\binit\?\B/, + /\binit!\B/, + /\binout\b/, + /\binternal\(set\)\B/, + /\binternal\b/, + /\bin\b/, + /\bis\b/, // operator + /\blazy\b/, // contextual + /\blet\b/, + /\bmutating\b/, // contextual + /\bnil\b/, // literal + /\bnonmutating\b/, // contextual + /\bopen\(set\)\B/, // contextual + /\bopen\b/, // contextual + /\boperator\b/, + /\boptional\b/, // contextual + /\boverride\b/, // contextual + /\bpostfix\b/, // contextual + /\bprecedencegroup\b/, + /\bprefix\b/, // contextual + /\bprivate\(set\)\B/, + /\bprivate\b/, + /\bprotocol\b/, + /\bpublic\(set\)\B/, + /\bpublic\b/, + /\brepeat\b/, + /\brequired\b/, // contextual + /\brethrows\b/, + /\breturn\b/, + /\bset\b/, // contextual + /\bsome\b/, // contextual + /\bstatic\b/, + /\bstruct\b/, + /\bsubscript\b/, + /\bsuper\b/, + /\bswitch\b/, + /\bthrows\b/, + /\bthrow\b/, + /\btrue\b/, // literal + /\btry\?\B/, // operator + /\btry!\B/, // operator + /\btry\b/, // operator + /\btypealias\b/, + /\bunowned\(safe\)\B/, // contextual + /\bunowned\(unsafe\)\B/, // contextual + /\bunowned\b/, // contextual + /\bvar\b/, + /\bweak\b/, // contextual + /\bwhere\b/, + /\bwhile\b/, + /\bwillSet\b/, // contextual +]; + +// NOTE: Contextual keywords are reserved only in specific contexts. +// Ideally, these should be matched using modes to avoid false positives. + +// TODO: Create a PRECEDENCE_GROUP mode to match the remaining contextual keywords: +// assignment associativity higherThan left lowerThan none right +// These aren't included in the list because they result in mostly false positives. + +// Keywords that start with a number character (#). +// #available is handled separately. +export const numberKeywords = [ + 'colorLiteral', + 'column', + 'dsohandle', + 'else', + 'elseif', + 'endif', + 'error', + 'file', + 'fileID', + 'fileLiteral', + 'filePath', + 'function', + 'if', + 'imageLiteral', + 'keyPath', + 'line', + 'selector', + 'sourceLocation', + 'warn_unqualified_access', + 'warning', +]; + +// Global functions in the Standard Library. +export const builtIns = [ + 'abs', + 'all', + 'any', + 'assert', + 'assertionFailure', + 'debugPrint', + 'dump', + 'fatalError', + 'getVaList', + 'isKnownUniquelyReferenced', + 'max', + 'min', + 'numericCast', + 'pointwiseMax', + 'pointwiseMin', + 'precondition', + 'preconditionFailure', + 'print', + 'readLine', + 'repeatElement', + 'sequence', + 'stride', + 'swap', + 'swift_unboxFromSwiftValueWithType', + 'transcode', + 'type', + 'unsafeBitCast', + 'unsafeDowncast', + 'withExtendedLifetime', + 'withUnsafeMutablePointer', + 'withUnsafePointer', + 'withVaList', + 'withoutActuallyEscaping', + 'zip', +]; + +// Valid first characters for operators. +export const operatorHead = either( + /[/=\-+!*%<>&|^~?]/, + /[\u00A1–\u00A7]/, + /[\u00A9\u00AB]/, + /[\u00AC\u00AE]/, + /[\u00B0\u00B1]/, + /[\u00B6\u00BB\u00BF\u00D7\u00F7]/, + /[\u2016–\u2017]/, + /[\u2020–\u2027]/, + /[\u2030–\u203E]/, + /[\u2041–\u2053]/, + /[\u2055–\u205E]/, + /[\u2190–\u23FF]/, + /[\u2500–\u2775]/, + /[\u2794–\u2BFF]/, + /[\u2E00–\u2E7F]/, + /[\u3001–\u3003]/, + /[\u3008–\u3020]/, + /[\u3030]/ +); + +// Valid characters for operators. +export const operatorCharacter = either( + operatorHead, + /[\u0300–\u036F]/, + /[\u1DC0–\u1DFF]/, + /[\u20D0–\u20FF]/, + /[\uFE00–\uFE0F]/, + /[\uFE20–\uFE2F]/, + // TODO: The following characters are also allowed, but the regex isn't supported yet. + // /[\u{E0100}–\u{E01EF}]/u +); + +// Valid first characters for identifiers. +export const identifierHead = either( + /[a-zA-Z_]/, + /[\u00A8\u00AA\u00AD\u00AF\u00B2–\u00B5\u00B7–\u00BA]/, + /[\u00BC–\u00BE\u00C0–\u00D6\u00D8–\u00F6\u00F8–\u00FF]/, + /[\u0100–\u02FF\u0370–\u167F\u1681–\u180D\u180F–\u1DBF]/, + /[\u1E00–\u1FFF]/, + /[\u200B–\u200D\u202A–\u202E\u203F–\u2040\u2054\u2060–\u206F]/, + /[\u2070–\u20CF\u2100–\u218F\u2460–\u24FF\u2776–\u2793]/, + /[\u2C00–\u2DFF\u2E80–\u2FFF]/, + /[\u3004–\u3007\u3021–\u302F\u3031–\u303F\u3040–\uD7FF]/, + /[\uF900–\uFD3D\uFD40–\uFDCF\uFDF0–\uFE1F\uFE30–\uFE44]/, + /[\uFE47–\uFFFD]/, + // 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, + // /[\u{90000}–\u{9FFFD}\u{A0000–\u{AFFFD}\u{B0000}–\u{BFFFD}\u{C0000}–\u{CFFFD}]/u, + // /[\u{D0000}–\u{DFFFD}\u{E0000–\u{EFFFD}]/u +); + +// Valid characters for identifiers. +export const identifierCharacter = either( + identifierHead, + /\d/, + /[\u0300–\u036F\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F]/ +); + +// Valid identifier. +export const identifier = concat(identifierHead, identifierCharacter, '*'); + +// Built-in attributes, which are highlighted as keywords. +// @available is handled separately. +export const keywordAttributes = [ + /autoclosure/, + concat(/convention\(/, either('swift', 'block', 'c'), /\)/), + /discardableResult/, + /dynamicCallable/, + /dynamicMemberLookup/, + /escaping/, + /frozen/, + /GKInspectable/, + /IBAction/, + /IBDesignable/, + /IBInspectable/, + /IBOutlet/, + /IBSegueAction/, + /inlinable/, + /main/, + /nonobjc/, + /NSApplicationMain/, + /NSCopying/, + /NSManaged/, + concat(/objc\(/, identifier, /\)/), + /objc/, + /objcMembers/, + /propertyWrapper/, + /requires_stored_property_inits/, + /testable/, + /UIApplicationMain/, + /unknown/, + /usableFromInline/, +]; + +// Contextual keywords used in @available and #available. +export const availabilityKeywords = [ + 'iOS', + 'iOSApplicationExtension', + 'macOS', + 'macOSApplicationExtension', + 'macCatalyst', + 'macCatalystApplicationExtension', + 'watchOS', + 'watchOSApplicationExtension', + 'tvOS', + 'tvOSApplicationExtension', + 'swift', +]; diff --git a/src/languages/swift.js b/src/languages/swift.js index a85fa6b120..f2affa2472 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -7,71 +7,60 @@ Website: https://swift.org Category: common, system */ -import { concat, either } from "../lib/regex"; +import * as Swift from './lib/swift.js'; +import { concat, either } from '../lib/regex'; /** @type LanguageFn */ export default function(hljs) { // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413 // https://docs.swift.org/swift-book/ReferenceManual/zzSummaryOfTheGrammar.html - const SWIFT_KEYWORDS = { - // Override the pattern since the default of /\w+/ is not sufficient to capture - // the keywords that start with a number sign (#) or that have parentheses. - $pattern: /#?\w+(\(\w+\))?/, - keyword: - // Reserved keywords. These require backticks to use as an identifier: - '_ Any as associatedtype break case catch class continue default defer deinit do else enum extension ' + - 'fallthrough fileprivate fileprivate(set) for func guard if import in init inout internal internal(set) is ' + - 'let operator precedencegroup private private(set) protocol public public(set) repeat rethrows return ' + - 'self Self static struct subscript super switch throw throws try typealias var where while ' + - // This list includes as, is, and try, even though they're technically operators. - // The remaining operators as?, as!, try?, and try! are declared in the OPERATOR mode. - - // Keywords that begin with a number sign: - '#available #colorLiteral #column #dsohandle #else #elseif #endif #error #file #fileID #fileLiteral #filePath ' + - '#function #if #imageLiteral #keyPath #line #selector #sourceLocation #warning ' + - - // Keywords reserved in particular contexts. These can be used as identifiers: - 'convenience didSet dynamic final get infix indirect lazy mutating nonmutating open open(set) ' + - 'optional override postfix prefix Protocol required set some Type unowned unowned(safe) unowned(unsafe) weak willSet ', - // Ideally, these keywords should only be matched in specific modes, as they can result in false positives. - // For example, the following keywords are commented out because they're only used in - // precedencegroup declarations, so they will likely result in mostly false positives: - // 'assignment associativity higherThan left lowerThan none right ' - literal: 'true false nil', - built_in: 'abs advance alignof alignofValue anyGenerator assert assertionFailure ' + - 'bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC ' + - 'bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros ' + - 'debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords ' + - 'enumerate equal fatalError filter find getBridgedObjectiveCType getVaList ' + - 'indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC ' + - 'isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare ' + - 'map max maxElement min minElement numericCast overlaps partition posix ' + - 'precondition preconditionFailure print println quickSort readLine reduce reflect ' + - 'reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split ' + - 'startsWith stride strideof strideofValue swap toString transcode ' + - 'underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap ' + - 'unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer ' + - 'withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers ' + - 'withUnsafePointer withUnsafePointers withVaList zip' + const DOT_KEYWORD = { + begin: concat(/\./, either(...Swift.dotKeywords, ...Swift.optionalDotKeywords)), + returnBegin: true, + contains: [ + { + begin: /\./ + }, + { + className: 'keyword', + begin: either(...Swift.dotKeywords, ...Swift.optionalDotKeywords) + } + ] + }; + const KEYWORD_GUARD = { + // Consume .keyword to prevent highlighting properties and methods as keywords. + begin: concat(/\./, either(...Swift.keywords)), + relevance: 0, + }; + const KEYWORD = { + className: 'keyword', + variants: [ + { begin: either(...Swift.keywords, ...Swift.optionalDotKeywords) }, + { begin: concat(/#/, either(...Swift.numberKeywords)) } + ] }; + const KEYWORDS = [DOT_KEYWORD, KEYWORD_GUARD, KEYWORD]; - const TYPE = { - className: 'type', - begin: '\\b[A-Z][\\w\u00C0-\u02B8\']*', - relevance: 0 + // 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)), + relevance: 0, }; - // slightly more special to swift - const OPTIONAL_USING_TYPE = { - className: 'type', - begin: '\\b[A-Z][\\w\u00C0-\u02B8\']*[!?]' + const BUILT_IN = { + className: 'built_in', + begin: concat(/\b/, either(...Swift.builtIns), /(?=\()/) + }; + const BUILT_INS = [BUILT_IN_GUARD, BUILT_IN]; + + // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418 + const OPERATOR = { + className: 'operator', + variants: [ + { begin: `${Swift.operatorHead}${Swift.operatorCharacter}*` }, + { begin: `\\.(\\.|${Swift.operatorCharacter})+` } + ] }; - const BLOCK_COMMENT = hljs.COMMENT( - '/\\*', - '\\*/', - { - contains: ['self'] - } - ); // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#grammar_numeric-literal // TODO: Update for leading `-` after lookbehind is supported everywhere @@ -138,81 +127,98 @@ export default function(hljs) { SINGLE_LINE_STRING("###"), ] }; - for (const variant of STRING.variants) { - const interpolation = variant.contains.find(mode => mode.label === "interpol"); - // TODO: Interpolation can contain any expression, so there's room for improvement here. - interpolation.contains = [STRING, NUMBER]; - } - // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418 - const operatorHead = either( - /[/=\-+!*%<>&|^~?]/, - /[\u00A1–\u00A7]/, - /[\u00A9\u00AB]/, - /[\u00AC\u00AE]/, - /[\u00B0\u00B1]/, - /[\u00B6\u00BB\u00BF\u00D7\u00F7]/, - /[\u2016–\u2017]/, - /[\u2020–\u2027]/, - /[\u2030–\u203E]/, - /[\u2041–\u2053]/, - /[\u2055–\u205E]/, - /[\u2190–\u23FF]/, - /[\u2500–\u2775]/, - /[\u2794–\u2BFF]/, - /[\u2E00–\u2E7F]/, - /[\u3001–\u3003]/, - /[\u3008–\u3020]/, - /[\u3030]/ - ); - const operatorCharacter = either( - operatorHead, - /[\u0300–\u036F]/, - /[\u1DC0–\u1DFF]/, - /[\u20D0–\u20FF]/, - /[\uFE00–\uFE0F]/, - /[\uFE20–\uFE2F]/, - // TODO: The following characters are also allowed, but the regex doesn't work as intended. - // For example, it also matches -10 as an operator. - // /[\u{E0100}–\u{E01EF}]/u - ); - const OPERATOR = { - className: 'operator', - variants: [ - { // TODO: Replace with negative look-behind when available. - className: 'keyword', - begin: /\s(?=as[?!]?\s)/, - excludeBegin: true, - end: /as[?!]?(?=\s)/ - }, - { // TODO: Replace with negative look-behind when available. - className: 'keyword', - begin: /\s(?=is\s)/, - excludeBegin: true, - end: /is(?=\s)/ - }, - { // TODO: Replace with negative look-behind when available. + // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID412 + const QUOTED_IDENTIFIER = { + begin: concat(/`/, Swift.identifier, /`/) + }; + const IMPLICIT_PARAMETER = { + className: 'variable', + begin: /\$\d+/ + }; + const PROPERTY_WRAPPER_PROJECTION = { + className: 'variable', + begin: `\\$${Swift.identifierCharacter}+` + }; + const IDENTIFIERS = [QUOTED_IDENTIFIER, IMPLICIT_PARAMETER, PROPERTY_WRAPPER_PROJECTION]; + + // https://docs.swift.org/swift-book/ReferenceManual/Attributes.html + const AVAILABLE = { + begin: /(@|#)available/, + returnBegin: true, + contains: [ + { className: 'keyword', - begin: /(^|[^.])(?=\btry[?!]?\s)/, - excludeBegin: true, - end: /try[?!]?(?=\s)/ + begin: /(@|#)available/ }, - { begin: `${operatorHead}${operatorCharacter}*` }, - { begin: `\\.(\\.|${operatorCharacter})+` } + { + begin: /\(/, + end: /\)/, + endsParent: true, + keywords: Swift.availabilityKeywords.join(' '), + contains: [ + OPERATOR, + NUMBER, + STRING, + ] + } ] }; + const KEYWORD_ATTRIBUTE = { + className: 'keyword', + begin: concat(/@/, either(...Swift.keywordAttributes)) + }; + const USER_DEFINED_ATTRIBUTE = { + className: 'meta', + begin: concat(/@/, Swift.identifier) + } + const ATTRIBUTES = [ + AVAILABLE, + KEYWORD_ATTRIBUTE, + USER_DEFINED_ATTRIBUTE + ]; + + const TYPE = { + className: 'type', + begin: '\\b[A-Z][\\w\u00C0-\u02B8\']*', + relevance: 0 + }; + // slightly more special to swift + const OPTIONAL_USING_TYPE = { + className: 'type', + begin: '\\b[A-Z][\\w\u00C0-\u02B8\']*[!?]' + }; + const BLOCK_COMMENT = hljs.COMMENT( + '/\\*', + '\\*/', + { + contains: ['self'] + } + ); + + // Add supported submodes to string interpolation. + for (const variant of STRING.variants) { + const interpolation = variant.contains.find(mode => mode.label === "interpol"); + // TODO: Interpolation can contain any expression, so there's room for improvement here. + const submodes = [...KEYWORDS, ...BUILT_INS, OPERATOR, NUMBER, STRING, ...IDENTIFIERS]; + interpolation.contains = [ + ...submodes, + { + begin: /\(/, + end: /\)/, + contains: [ + 'self', + ...submodes + ] + } + ]; + } return { name: 'Swift', - keywords: SWIFT_KEYWORDS, contains: [ - STRING, hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT, - OPERATOR, - OPTIONAL_USING_TYPE, - TYPE, - NUMBER, { className: 'function', beginKeywords: 'func', end: /\{/, excludeEnd: true, @@ -226,9 +232,9 @@ export default function(hljs) { { className: 'params', begin: /\(/, end: /\)/, endsParent: true, - keywords: SWIFT_KEYWORDS, contains: [ 'self', + ...KEYWORDS, NUMBER, STRING, hljs.C_BLOCK_COMMENT_MODE, @@ -242,28 +248,27 @@ export default function(hljs) { { className: 'class', beginKeywords: 'struct protocol class extension enum', - keywords: SWIFT_KEYWORDS, end: '\\{', excludeEnd: true, contains: [ - hljs.inherit(hljs.TITLE_MODE, {begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}) + hljs.inherit(hljs.TITLE_MODE, {begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}), + ...KEYWORDS ] }, - { - className: 'meta', // @attributes - begin: '(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|' + - '@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|' + - '@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|' + - '@infix|@prefix|@postfix|@autoclosure|@testable|@available|' + - '@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|' + - '@propertyWrapper|@main)\\b' - - }, { beginKeywords: 'import', end: /$/, contains: [hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT], relevance: 0 - } + }, + ...KEYWORDS, + ...BUILT_INS, + OPERATOR, + NUMBER, + STRING, + ...IDENTIFIERS, + ...ATTRIBUTES, + OPTIONAL_USING_TYPE, + TYPE, ] }; } diff --git a/test/markup/swift/attributes.expect.txt b/test/markup/swift/attributes.expect.txt new file mode 100644 index 0000000000..1b9dfb319b --- /dev/null +++ b/test/markup/swift/attributes.expect.txt @@ -0,0 +1,9 @@ +@available(iOS 14, deprecated: "reason", *) +@convention(swift) +@objc +@objc(name) + +@propertyWrapper +@SomeWrapper(value: 1.0, other: "string", bool: false) + +@ notAnAttribute diff --git a/test/markup/swift/attributes.txt b/test/markup/swift/attributes.txt new file mode 100644 index 0000000000..3104b40cfa --- /dev/null +++ b/test/markup/swift/attributes.txt @@ -0,0 +1,9 @@ +@available(iOS 14, deprecated: "reason", *) +@convention(swift) +@objc +@objc(name) + +@propertyWrapper +@SomeWrapper(value: 1.0, other: "string", bool: false) + +@ notAnAttribute diff --git a/test/markup/swift/functions.expect.txt b/test/markup/swift/functions.expect.txt index a83afdfb6b..5031d0dcb9 100644 --- a/test/markup/swift/functions.expect.txt +++ b/test/markup/swift/functions.expect.txt @@ -5,6 +5,6 @@ class MyClass { func f() { - return true + return true } } diff --git a/test/markup/swift/identifiers.expect.txt b/test/markup/swift/identifiers.expect.txt new file mode 100644 index 0000000000..a56b2fb051 --- /dev/null +++ b/test/markup/swift/identifiers.expect.txt @@ -0,0 +1,3 @@ +`func` +{ $0 + 1 } +value.$wrappedValue diff --git a/test/markup/swift/identifiers.txt b/test/markup/swift/identifiers.txt new file mode 100644 index 0000000000..be00a808cc --- /dev/null +++ b/test/markup/swift/identifiers.txt @@ -0,0 +1,3 @@ +`func` +{ $0 + 1 } +value.$wrappedValue diff --git a/test/markup/swift/keywords.expect.txt b/test/markup/swift/keywords.expect.txt new file mode 100644 index 0000000000..6bb2ad24f0 --- /dev/null +++ b/test/markup/swift/keywords.expect.txt @@ -0,0 +1,25 @@ +Sequence.Protocol Protocol +String.Type Type + +String.init init +String.self self + +Any Self +(_ name: String) +x as Int +x as? Double +x as! String +x is String +init?() init!() init +try? try! try +true false nil +fileprivate(set) internal(set) open(set) private(set) public(set) +unowned(safe) unowned(unsafe) + +#if +#error("Error") +#endif + +x.as(y) +x.for(y) +#notAKeyword diff --git a/test/markup/swift/keywords.txt b/test/markup/swift/keywords.txt new file mode 100644 index 0000000000..6b354c0db1 --- /dev/null +++ b/test/markup/swift/keywords.txt @@ -0,0 +1,25 @@ +Sequence.Protocol Protocol +String.Type Type + +String.init init +String.self self + +Any Self +(_ name: String) +x as Int +x as? Double +x as! String +x is String +init?() init!() init +try? try! try +true false nil +fileprivate(set) internal(set) open(set) private(set) public(set) +unowned(safe) unowned(unsafe) + +#if +#error("Error") +#endif + +x.as(y) +x.for(y) +#notAKeyword diff --git a/test/markup/swift/operators.expect.txt b/test/markup/swift/operators.expect.txt index 817d2b84c4..33ccd3d3bf 100644 --- a/test/markup/swift/operators.expect.txt +++ b/test/markup/swift/operators.expect.txt @@ -1,15 +1,4 @@ -x is y -x.is(y) -x as y -x as? y -x as! y -a.as(y) -try x -try? x -try! x -x.try(y) - -!true +!true ~x +1 -1 @@ -81,10 +70,10 @@ a .> b a.>b a .>= b a.>=b -true && false -true&&false -true || false -true||false +true && false +true&&false +true || false +true||false a = 1 a=1 a *= 1 diff --git a/test/markup/swift/operators.txt b/test/markup/swift/operators.txt index 17d484c4f2..b287b06e6b 100644 --- a/test/markup/swift/operators.txt +++ b/test/markup/swift/operators.txt @@ -1,14 +1,3 @@ -x is y -x.is(y) -x as y -x as? y -x as! y -a.as(y) -try x -try? x -try! x -x.try(y) - !true ~x +1 diff --git a/test/markup/swift/strings.expect.txt b/test/markup/swift/strings.expect.txt index 88e36b6c90..f310eb8678 100644 --- a/test/markup/swift/strings.expect.txt +++ b/test/markup/swift/strings.expect.txt @@ -53,16 +53,16 @@ escaped newline \### ###"interpolation \###("string") raw \##("string") raw \#("string") raw \("string")"### """ -interpolation \(x) +interpolation \($0 + 1) """ #""" -interpolation \#(123) -raw \(123) +interpolation \#(abs(x - 2) as Double) +raw \(abs(x - 2) as Double) """# ##""" -interpolation \##(1.23) -raw \#(1.23) -raw \(1.23) +interpolation \##(true) +raw \#(true) +raw \(true) """## ###""" interpolation \###("string") diff --git a/test/markup/swift/strings.txt b/test/markup/swift/strings.txt index 7960261b43..6b177636a1 100644 --- a/test/markup/swift/strings.txt +++ b/test/markup/swift/strings.txt @@ -53,16 +53,16 @@ same line ###"interpolation \###("string") raw \##("string") raw \#("string") raw \("string")"### """ -interpolation \(x) +interpolation \($0 + 1) """ #""" -interpolation \#(123) -raw \(123) +interpolation \#(abs(x - 2) as Double) +raw \(abs(x - 2) as Double) """# ##""" -interpolation \##(1.23) -raw \#(1.23) -raw \(1.23) +interpolation \##(true) +raw \#(true) +raw \(true) """## ###""" interpolation \###("string") diff --git a/test/markup/swift/swiftui.expect.txt b/test/markup/swift/swiftui.expect.txt index 0d9fae6261..3b55db4dc3 100644 --- a/test/markup/swift/swiftui.expect.txt +++ b/test/markup/swift/swiftui.expect.txt @@ -1,4 +1,4 @@ -@main +@main struct MyApp: App { var body: some Scene { WindowGroup { From 09be110d9e80165e6ef87c9001b6a9c996c37e12 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Thu, 10 Dec 2020 18:58:57 +0100 Subject: [PATCH 06/17] (swift) Update author and changelog. --- CHANGES.md | 8 ++++++-- src/languages/swift.js | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 122cbe97fb..c9d4d13bf9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,8 +7,12 @@ New Languages: Language improvements: - enh(makefile): Add `make` as an alias (#2883) [tripleee][] -- enh(swift) Improved grammar for strings (#2819) [Steven Van Impe][] -- enh(swift) Improvements to keywords and built-ins and added support for operators and attributes (#2908) [Steven Van Impe][] +- enh(swift) Grammar improvements (#2908) [Steven Van Impe][] + - New grammar for keywords and built-ins + - Added support for operator highlighting + - New grammar for attributes + - Added support for quoted identifiers, implicit parameters, and property wrapper projections + - Support for more complex expressions in string interpolation - fix(asciidoc): Handle section titles level 5 (#2868) [Vaibhav Chanana][] Grammar improvements: diff --git a/src/languages/swift.js b/src/languages/swift.js index f2affa2472..a694a25fc1 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -1,8 +1,8 @@ /* Language: Swift Description: Swift is a general-purpose programming language built using a modern approach to safety, performance, and software design patterns. -Author: Chris Eidhof -Contributors: Nate Cook , Alexander Lichter , Steven Van Impe +Author: Steven Van Impe +Contributors: Chris Eidhof , Nate Cook , Alexander Lichter , Richard Gibson Website: https://swift.org Category: common, system */ From 1026ac03e32d74202607bca4926994bed34f81d2 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Fri, 11 Dec 2020 14:30:19 +0100 Subject: [PATCH 07/17] Linter fixes --- src/languages/lib/swift.js | 21 +++--- src/languages/swift.js | 146 +++++++++++++++++++++++++------------ 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/src/languages/lib/swift.js b/src/languages/lib/swift.js index 6c04d552de..84e8087e66 100644 --- a/src/languages/lib/swift.js +++ b/src/languages/lib/swift.js @@ -1,9 +1,12 @@ -import { concat, either } from '../../lib/regex'; +import { + concat, + either +} from '../../lib/regex.js'; // Keywords that require a leading dot. export const dotKeywords = [ /Protocol\b/, // contextual - /Type\b/, // contextual + /Type\b/ // contextual ]; // Keywords that may have a leading dot. @@ -99,7 +102,7 @@ export const keywords = [ /\bweak\b/, // contextual /\bwhere\b/, /\bwhile\b/, - /\bwillSet\b/, // contextual + /\bwillSet\b/ // contextual ]; // NOTE: Contextual keywords are reserved only in specific contexts. @@ -131,7 +134,7 @@ export const numberKeywords = [ 'selector', 'sourceLocation', 'warn_unqualified_access', - 'warning', + 'warning' ]; // Global functions in the Standard Library. @@ -169,7 +172,7 @@ export const builtIns = [ 'withUnsafePointer', 'withVaList', 'withoutActuallyEscaping', - 'zip', + 'zip' ]; // Valid first characters for operators. @@ -201,7 +204,7 @@ export const operatorCharacter = either( /[\u1DC0–\u1DFF]/, /[\u20D0–\u20FF]/, /[\uFE00–\uFE0F]/, - /[\uFE20–\uFE2F]/, + /[\uFE20–\uFE2F]/ // TODO: The following characters are also allowed, but the regex isn't supported yet. // /[\u{E0100}–\u{E01EF}]/u ); @@ -218,7 +221,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–\uFFFD]/ // 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, @@ -266,7 +269,7 @@ export const keywordAttributes = [ /testable/, /UIApplicationMain/, /unknown/, - /usableFromInline/, + /usableFromInline/ ]; // Contextual keywords used in @available and #available. @@ -281,5 +284,5 @@ export const availabilityKeywords = [ 'watchOSApplicationExtension', 'tvOS', 'tvOSApplicationExtension', - 'swift', + 'swift' ]; diff --git a/src/languages/swift.js b/src/languages/swift.js index a694a25fc1..f42d4a87fd 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -8,7 +8,10 @@ Category: common, system */ import * as Swift from './lib/swift.js'; -import { concat, either } from '../lib/regex'; +import { + concat, + either +} from '../lib/regex.js'; /** @type LanguageFn */ export default function(hljs) { @@ -30,35 +33,50 @@ export default function(hljs) { const KEYWORD_GUARD = { // Consume .keyword to prevent highlighting properties and methods as keywords. begin: concat(/\./, either(...Swift.keywords)), - relevance: 0, + relevance: 0 }; const KEYWORD = { className: 'keyword', variants: [ - { begin: either(...Swift.keywords, ...Swift.optionalDotKeywords) }, - { begin: concat(/#/, either(...Swift.numberKeywords)) } + { + begin: either(...Swift.keywords, ...Swift.optionalDotKeywords) + }, + { + begin: concat(/#/, either(...Swift.numberKeywords)) + } ] }; - const KEYWORDS = [DOT_KEYWORD, KEYWORD_GUARD, KEYWORD]; + const KEYWORDS = [ + DOT_KEYWORD, + KEYWORD_GUARD, + KEYWORD + ]; // 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)), - relevance: 0, + relevance: 0 }; const BUILT_IN = { className: 'built_in', begin: concat(/\b/, either(...Swift.builtIns), /(?=\()/) }; - const BUILT_INS = [BUILT_IN_GUARD, BUILT_IN]; + const BUILT_INS = [ + BUILT_IN_GUARD, + BUILT_IN + ]; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418 const OPERATOR = { className: 'operator', variants: [ - { begin: `${Swift.operatorHead}${Swift.operatorCharacter}*` }, - { begin: `\\.(\\.|${Swift.operatorCharacter})+` } + { + begin: `${Swift.operatorHead}${Swift.operatorCharacter}*` + }, + { + begin: `\\.(\\.|${Swift.operatorCharacter})+` + } ] }; @@ -67,31 +85,38 @@ export default function(hljs) { const decimalDigits = '([0-9]_*)+'; const hexDigits = '([0-9a-fA-F]_*)+'; const NUMBER = { - className: 'number', - relevance: 0, - variants: [ - // decimal floating-point-literal (subsumes decimal-literal) - { begin: `\\b(${decimalDigits})(\\.(${decimalDigits}))?` + - `([eE][+-]?(${decimalDigits}))?\\b` }, - - // hexadecimal floating-point-literal (subsumes hexadecimal-literal) - { begin: `\\b0x(${hexDigits})(\\.(${hexDigits}))?` + - `([pP][+-]?(${decimalDigits}))?\\b` }, - - // octal-literal - { begin: /\b0o([0-7]_*)+\b/ }, - - // binary-literal - { begin: /\b0b([01]_*)+\b/ }, - ] + className: 'number', + relevance: 0, + variants: [ + // decimal floating-point-literal (subsumes decimal-literal) + { + begin: `\\b(${decimalDigits})(\\.(${decimalDigits}))?` + `([eE][+-]?(${decimalDigits}))?\\b` + }, + // hexadecimal floating-point-literal (subsumes hexadecimal-literal) + { + begin: `\\b0x(${hexDigits})(\\.(${hexDigits}))?` + `([pP][+-]?(${decimalDigits}))?\\b` + }, + // octal-literal + { + begin: /\b0o([0-7]_*)+\b/ + }, + // binary-literal + { + begin: /\b0b([01]_*)+\b/ + } + ] }; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#grammar_string-literal const ESCAPED_CHARACTER = (rawDelimiter = "") => ({ className: 'subst', variants: [ - { begin: concat(/\\/, rawDelimiter, /[0\\tnr"']/) }, - { begin: concat(/\\/, rawDelimiter, /u\{[0-9a-fA-F]{1,8}\}/) } + { + begin: concat(/\\/, rawDelimiter, /[0\\tnr"']/) + }, + { + begin: concat(/\\/, rawDelimiter, /u\{[0-9a-fA-F]{1,8}\}/) + } ] }); const ESCAPED_NEWLINE = (rawDelimiter = "") => ({ @@ -107,12 +132,19 @@ export default function(hljs) { const MULTILINE_STRING = (rawDelimiter = "") => ({ begin: concat(rawDelimiter, /"""/), end: concat(/"""/, rawDelimiter), - contains: [ESCAPED_CHARACTER(rawDelimiter), ESCAPED_NEWLINE(rawDelimiter), INTERPOLATION(rawDelimiter)] + contains: [ + ESCAPED_CHARACTER(rawDelimiter), + ESCAPED_NEWLINE(rawDelimiter), + INTERPOLATION(rawDelimiter) + ] }); const SINGLE_LINE_STRING = (rawDelimiter = "") => ({ begin: concat(rawDelimiter, /"/), end: concat(/"/, rawDelimiter), - contains: [ESCAPED_CHARACTER(rawDelimiter), INTERPOLATION(rawDelimiter)] + contains: [ + ESCAPED_CHARACTER(rawDelimiter), + INTERPOLATION(rawDelimiter) + ] }); const STRING = { className: 'string', @@ -124,7 +156,7 @@ export default function(hljs) { SINGLE_LINE_STRING(), SINGLE_LINE_STRING("#"), SINGLE_LINE_STRING("##"), - SINGLE_LINE_STRING("###"), + SINGLE_LINE_STRING("###") ] }; @@ -140,7 +172,11 @@ export default function(hljs) { className: 'variable', begin: `\\$${Swift.identifierCharacter}+` }; - const IDENTIFIERS = [QUOTED_IDENTIFIER, IMPLICIT_PARAMETER, PROPERTY_WRAPPER_PROJECTION]; + const IDENTIFIERS = [ + QUOTED_IDENTIFIER, + IMPLICIT_PARAMETER, + PROPERTY_WRAPPER_PROJECTION + ]; // https://docs.swift.org/swift-book/ReferenceManual/Attributes.html const AVAILABLE = { @@ -159,7 +195,7 @@ export default function(hljs) { contains: [ OPERATOR, NUMBER, - STRING, + STRING ] } ] @@ -171,7 +207,7 @@ export default function(hljs) { const USER_DEFINED_ATTRIBUTE = { className: 'meta', begin: concat(/@/, Swift.identifier) - } + }; const ATTRIBUTES = [ AVAILABLE, KEYWORD_ATTRIBUTE, @@ -192,15 +228,22 @@ export default function(hljs) { '/\\*', '\\*/', { - contains: ['self'] + contains: [ 'self' ] } ); - + // Add supported submodes to string interpolation. for (const variant of STRING.variants) { const interpolation = variant.contains.find(mode => mode.label === "interpol"); // TODO: Interpolation can contain any expression, so there's room for improvement here. - const submodes = [...KEYWORDS, ...BUILT_INS, OPERATOR, NUMBER, STRING, ...IDENTIFIERS]; + const submodes = [ + ...KEYWORDS, + ...BUILT_INS, + OPERATOR, + NUMBER, + STRING, + ...IDENTIFIERS + ]; interpolation.contains = [ ...submodes, { @@ -221,24 +264,31 @@ export default function(hljs) { BLOCK_COMMENT, { className: 'function', - beginKeywords: 'func', end: /\{/, excludeEnd: true, + beginKeywords: 'func', + end: /\{/, + excludeEnd: true, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /[A-Za-z$_][0-9A-Za-z$_]*/ }), { - begin: // + begin: // }, { className: 'params', - begin: /\(/, end: /\)/, endsParent: true, + begin: /\(/, + end: /\)/, + endsParent: true, contains: [ 'self', ...KEYWORDS, NUMBER, STRING, hljs.C_BLOCK_COMMENT_MODE, - {begin: ':'} // relevance booster + { // relevance booster + begin: ':' + } ], illegal: /["']/ } @@ -251,13 +301,19 @@ export default function(hljs) { end: '\\{', excludeEnd: true, contains: [ - hljs.inherit(hljs.TITLE_MODE, {begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}), + hljs.inherit(hljs.TITLE_MODE, { + begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/ + }), ...KEYWORDS ] }, { - beginKeywords: 'import', end: /$/, - contains: [hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT], + beginKeywords: 'import', + end: /$/, + contains: [ + hljs.C_LINE_COMMENT_MODE, + BLOCK_COMMENT + ], relevance: 0 }, ...KEYWORDS, @@ -268,7 +324,7 @@ export default function(hljs) { ...IDENTIFIERS, ...ATTRIBUTES, OPTIONAL_USING_TYPE, - TYPE, + TYPE ] }; } From da01153e079d1e80f35ad8f494a70f35f54af347 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Fri, 11 Dec 2020 14:46:50 +0100 Subject: [PATCH 08/17] Highlight literals as literals again. --- src/languages/lib/swift.js | 14 +++++++++----- src/languages/swift.js | 9 +++++++-- test/markup/swift/attributes.expect.txt | 2 +- test/markup/swift/functions.expect.txt | 2 +- test/markup/swift/keywords.expect.txt | 2 +- test/markup/swift/operators.expect.txt | 10 +++++----- test/markup/swift/strings.expect.txt | 2 +- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/languages/lib/swift.js b/src/languages/lib/swift.js index 84e8087e66..8465296f52 100644 --- a/src/languages/lib/swift.js +++ b/src/languages/lib/swift.js @@ -40,7 +40,6 @@ export const keywords = [ /\benum\b/, /\bextension\b/, /\bfallthrough\b/, - /\bfalse\b/, // literal /\bfileprivate\(set\)\B/, /\bfileprivate\b/, /\bfinal\b/, // contextual @@ -62,7 +61,6 @@ export const keywords = [ /\blazy\b/, // contextual /\blet\b/, /\bmutating\b/, // contextual - /\bnil\b/, // literal /\bnonmutating\b/, // contextual /\bopen\(set\)\B/, // contextual /\bopen\b/, // contextual @@ -90,7 +88,6 @@ export const keywords = [ /\bswitch\b/, /\bthrows\b/, /\bthrow\b/, - /\btrue\b/, // literal /\btry\?\B/, // operator /\btry!\B/, // operator /\btry\b/, // operator @@ -112,9 +109,16 @@ export const keywords = [ // assignment associativity higherThan left lowerThan none right // These aren't included in the list because they result in mostly false positives. -// Keywords that start with a number character (#). +// Literals. +export const literals = [ + /\bfalse\b/, + /\bnil\b/, + /\btrue\b/ +]; + +// Keywords that start with a number sign (#). // #available is handled separately. -export const numberKeywords = [ +export const numberSignKeywords = [ 'colorLiteral', 'column', 'dsohandle', diff --git a/src/languages/swift.js b/src/languages/swift.js index f42d4a87fd..02d9832e4c 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -36,13 +36,18 @@ export default function(hljs) { relevance: 0 }; const KEYWORD = { - className: 'keyword', variants: [ { + className: 'keyword', begin: either(...Swift.keywords, ...Swift.optionalDotKeywords) }, { - begin: concat(/#/, either(...Swift.numberKeywords)) + className: 'literal', + begin: either(...Swift.literals) + }, + { + className: 'keyword', + begin: concat(/#/, either(...Swift.numberSignKeywords)) } ] }; diff --git a/test/markup/swift/attributes.expect.txt b/test/markup/swift/attributes.expect.txt index 1b9dfb319b..78936775ff 100644 --- a/test/markup/swift/attributes.expect.txt +++ b/test/markup/swift/attributes.expect.txt @@ -4,6 +4,6 @@ @objc(name) @propertyWrapper -@SomeWrapper(value: 1.0, other: "string", bool: false) +@SomeWrapper(value: 1.0, other: "string", bool: false) @ notAnAttribute diff --git a/test/markup/swift/functions.expect.txt b/test/markup/swift/functions.expect.txt index 5031d0dcb9..a83afdfb6b 100644 --- a/test/markup/swift/functions.expect.txt +++ b/test/markup/swift/functions.expect.txt @@ -5,6 +5,6 @@ class MyClass { func f() { - return true + return true } } diff --git a/test/markup/swift/keywords.expect.txt b/test/markup/swift/keywords.expect.txt index 6bb2ad24f0..a7f03bca5e 100644 --- a/test/markup/swift/keywords.expect.txt +++ b/test/markup/swift/keywords.expect.txt @@ -12,7 +12,7 @@ x as! String x is String init?() init!() init try? try! try -true false nil +true false nil fileprivate(set) internal(set) open(set) private(set) public(set) unowned(safe) unowned(unsafe) diff --git a/test/markup/swift/operators.expect.txt b/test/markup/swift/operators.expect.txt index 33ccd3d3bf..99af8f6843 100644 --- a/test/markup/swift/operators.expect.txt +++ b/test/markup/swift/operators.expect.txt @@ -1,4 +1,4 @@ -!true +!true ~x +1 -1 @@ -70,10 +70,10 @@ a .> b a.>b a .>= b a.>=b -true && false -true&&false -true || false -true||false +true && false +true&&false +true || false +true||false a = 1 a=1 a *= 1 diff --git a/test/markup/swift/strings.expect.txt b/test/markup/swift/strings.expect.txt index f310eb8678..8de232baf3 100644 --- a/test/markup/swift/strings.expect.txt +++ b/test/markup/swift/strings.expect.txt @@ -60,7 +60,7 @@ interpolation \#(abs raw \(abs(x - 2) as Double) """# ##""" -interpolation \##(true) +interpolation \##(true) raw \#(true) raw \(true) """## From 2a1163e6e4f85ffe839af4a7b71266b3ccdc6136 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Fri, 11 Dec 2020 15:17:33 +0100 Subject: [PATCH 09/17] More keyword fixes. --- src/languages/lib/swift.js | 245 +++++++++++++++++++------------------ src/languages/swift.js | 5 + 2 files changed, 130 insertions(+), 120 deletions(-) diff --git a/src/languages/lib/swift.js b/src/languages/lib/swift.js index 8465296f52..974ed45daa 100644 --- a/src/languages/lib/swift.js +++ b/src/languages/lib/swift.js @@ -3,104 +3,109 @@ import { either } from '../../lib/regex.js'; +const keywordWrapper = keyword => concat( + /\b/, + keyword, + /\w$/.test(keyword) ? /\b/ : /\B/ +); + // Keywords that require a leading dot. export const dotKeywords = [ - /Protocol\b/, // contextual - /Type\b/ // contextual -]; + 'Protocol', // contextual + 'Type' // contextual +].map(keywordWrapper); // Keywords that may have a leading dot. export const optionalDotKeywords = [ - /init\b/, - /self\b/ -]; + 'init', + 'self' +].map(keywordWrapper); // Regular keywords and literals. export const keywords = [ - /\bAny\b/, - /\bSelf\b/, - /\b_\b/, // pattern - /\bassociatedtype\b/, - /\bas\?\B/, // operator - /\bas!\B/, // operator - /\bas\b/, // operator - /\bbreak\b/, - /\bcase\b/, - /\bcatch\b/, - /\bclass\b/, - /\bcontinue\b/, - /\bconvenience\b/, // contextual - /\bdefault\b/, - /\bdefer\b/, - /\bdeinit\b/, - /\bdidSet\b/, // contextual - /\bdo\b/, - /\bdynamic\b/, // contextual - /\belse\b/, - /\benum\b/, - /\bextension\b/, - /\bfallthrough\b/, - /\bfileprivate\(set\)\B/, - /\bfileprivate\b/, - /\bfinal\b/, // contextual - /\bfor\b/, - /\bfunc\b/, - /\bget\b/, // contextual - /\bguard\b/, - /\bif\b/, - /\bimport\b/, - /\bindirect\b/, // contextual - /\binfix\b/, // contextual - /\binit\?\B/, - /\binit!\B/, - /\binout\b/, - /\binternal\(set\)\B/, - /\binternal\b/, - /\bin\b/, - /\bis\b/, // operator - /\blazy\b/, // contextual - /\blet\b/, - /\bmutating\b/, // contextual - /\bnonmutating\b/, // contextual - /\bopen\(set\)\B/, // contextual - /\bopen\b/, // contextual - /\boperator\b/, - /\boptional\b/, // contextual - /\boverride\b/, // contextual - /\bpostfix\b/, // contextual - /\bprecedencegroup\b/, - /\bprefix\b/, // contextual - /\bprivate\(set\)\B/, - /\bprivate\b/, - /\bprotocol\b/, - /\bpublic\(set\)\B/, - /\bpublic\b/, - /\brepeat\b/, - /\brequired\b/, // contextual - /\brethrows\b/, - /\breturn\b/, - /\bset\b/, // contextual - /\bsome\b/, // contextual - /\bstatic\b/, - /\bstruct\b/, - /\bsubscript\b/, - /\bsuper\b/, - /\bswitch\b/, - /\bthrows\b/, - /\bthrow\b/, - /\btry\?\B/, // operator - /\btry!\B/, // operator - /\btry\b/, // operator - /\btypealias\b/, - /\bunowned\(safe\)\B/, // contextual - /\bunowned\(unsafe\)\B/, // contextual - /\bunowned\b/, // contextual - /\bvar\b/, - /\bweak\b/, // contextual - /\bwhere\b/, - /\bwhile\b/, - /\bwillSet\b/ // contextual -]; + 'Any', + 'Self', + 'associatedtype', + 'as\\?', // operator + 'as!', // operator + 'as', // operator + 'break', + 'case', + 'catch', + 'class', + 'continue', + 'convenience', // contextual + 'default', + 'defer', + 'deinit', + 'didSet', // contextual + 'do', + 'dynamic', // contextual + 'else', + 'enum', + 'extension', + 'fallthrough', + 'fileprivate\\(set\\)', + 'fileprivate', + 'final', // contextual + 'for', + 'func', + 'get', // contextual + 'guard', + 'if', + 'import', + 'indirect', // contextual + 'infix', // contextual + 'init\\?', + 'init!', + 'inout', + 'internal\\(set\\)', + 'internal', + 'in', + 'is', // operator + 'lazy', // contextual + 'let', + 'mutating', // contextual + 'nonmutating', // contextual + 'open\\(set\\)', // contextual + 'open', // contextual + 'operator', + 'optional', // contextual + 'override', // contextual + 'postfix', // contextual + 'precedencegroup', + 'prefix', // contextual + 'private\\(set\\)', + 'private', + 'protocol', + 'public\\(set\\)', + 'public', + 'repeat', + 'required', // contextual + 'rethrows', + 'return', + 'set', // contextual + 'some', // contextual + 'static', + 'struct', + 'subscript', + 'super', + 'switch', + 'throws', + 'throw', + 'try\\?', // operator + 'try!', // operator + 'try', // operator + 'typealias', + 'unowned\\(safe\\)', // contextual + 'unowned\\(unsafe\\)', // contextual + 'unowned', // contextual + 'var', + 'weak', // contextual + 'where', + 'while', + 'willSet' // contextual +].map(keywordWrapper); // NOTE: Contextual keywords are reserved only in specific contexts. // Ideally, these should be matched using modes to avoid false positives. @@ -111,10 +116,10 @@ export const keywords = [ // Literals. export const literals = [ - /\bfalse\b/, - /\bnil\b/, - /\btrue\b/ -]; + 'false', + 'nil', + 'true' +].map(keywordWrapper); // Keywords that start with a number sign (#). // #available is handled separately. @@ -246,34 +251,34 @@ export const identifier = concat(identifierHead, identifierCharacter, '*'); // Built-in attributes, which are highlighted as keywords. // @available is handled separately. export const keywordAttributes = [ - /autoclosure/, + 'autoclosure', concat(/convention\(/, either('swift', 'block', 'c'), /\)/), - /discardableResult/, - /dynamicCallable/, - /dynamicMemberLookup/, - /escaping/, - /frozen/, - /GKInspectable/, - /IBAction/, - /IBDesignable/, - /IBInspectable/, - /IBOutlet/, - /IBSegueAction/, - /inlinable/, - /main/, - /nonobjc/, - /NSApplicationMain/, - /NSCopying/, - /NSManaged/, + 'discardableResult', + 'dynamicCallable', + 'dynamicMemberLookup', + 'escaping', + 'frozen', + 'GKInspectable', + 'IBAction', + 'IBDesignable', + 'IBInspectable', + 'IBOutlet', + 'IBSegueAction', + 'inlinable', + 'main', + 'nonobjc', + 'NSApplicationMain', + 'NSCopying', + 'NSManaged', concat(/objc\(/, identifier, /\)/), - /objc/, - /objcMembers/, - /propertyWrapper/, - /requires_stored_property_inits/, - /testable/, - /UIApplicationMain/, - /unknown/, - /usableFromInline/ + 'objc', + 'objcMembers', + 'propertyWrapper', + 'requires_stored_property_inits', + 'testable', + 'UIApplicationMain', + 'unknown', + 'usableFromInline' ]; // Contextual keywords used in @available and #available. diff --git a/src/languages/swift.js b/src/languages/swift.js index 02d9832e4c..3a6f204ee6 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -45,6 +45,11 @@ export default function(hljs) { className: 'literal', begin: either(...Swift.literals) }, + { + className: 'keyword', + begin: /\b_\b/, + relevance: 0 + }, { className: 'keyword', begin: concat(/#/, either(...Swift.numberSignKeywords)) From f8510bdfef51e6b07851ff9f17e7d7f6aafbaf45 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Fri, 11 Dec 2020 18:37:14 +0100 Subject: [PATCH 10/17] Remove returnBegin and fix relevances --- src/languages/lib/swift.js | 70 ++++++++++++++++++++------------------ src/languages/swift.js | 33 ++++++++---------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/languages/lib/swift.js b/src/languages/lib/swift.js index 974ed45daa..4a988a4890 100644 --- a/src/languages/lib/swift.js +++ b/src/languages/lib/swift.js @@ -1,3 +1,4 @@ +/* eslint-disable no-misleading-character-class */ import { concat, either @@ -187,62 +188,65 @@ export const builtIns = [ // Valid first characters for operators. export const operatorHead = either( /[/=\-+!*%<>&|^~?]/, - /[\u00A1–\u00A7]/, + /[\u00A1-\u00A7]/, /[\u00A9\u00AB]/, /[\u00AC\u00AE]/, /[\u00B0\u00B1]/, /[\u00B6\u00BB\u00BF\u00D7\u00F7]/, - /[\u2016–\u2017]/, - /[\u2020–\u2027]/, - /[\u2030–\u203E]/, - /[\u2041–\u2053]/, - /[\u2055–\u205E]/, - /[\u2190–\u23FF]/, - /[\u2500–\u2775]/, - /[\u2794–\u2BFF]/, - /[\u2E00–\u2E7F]/, - /[\u3001–\u3003]/, - /[\u3008–\u3020]/, + /[\u2016-\u2017]/, + /[\u2020-\u2027]/, + /[\u2030-\u203E]/, + /[\u2041-\u2053]/, + /[\u2055-\u205E]/, + /[\u2190-\u23FF]/, + /[\u2500-\u2775]/, + /[\u2794-\u2BFF]/, + /[\u2E00-\u2E7F]/, + /[\u3001-\u3003]/, + /[\u3008-\u3020]/, /[\u3030]/ ); // Valid characters for operators. export const operatorCharacter = either( operatorHead, - /[\u0300–\u036F]/, - /[\u1DC0–\u1DFF]/, - /[\u20D0–\u20FF]/, - /[\uFE00–\uFE0F]/, - /[\uFE20–\uFE2F]/ + /[\u0300-\u036F]/, + /[\u1DC0-\u1DFF]/, + /[\u20D0-\u20FF]/, + /[\uFE00-\uFE0F]/, + /[\uFE20-\uFE2F]/ // TODO: The following characters are also allowed, but the regex isn't supported yet. - // /[\u{E0100}–\u{E01EF}]/u + // /[\u{E0100}-\u{E01EF}]/u ); // Valid first characters for identifiers. export const identifierHead = either( /[a-zA-Z_]/, - /[\u00A8\u00AA\u00AD\u00AF\u00B2–\u00B5\u00B7–\u00BA]/, - /[\u00BC–\u00BE\u00C0–\u00D6\u00D8–\u00F6\u00F8–\u00FF]/, - /[\u0100–\u02FF\u0370–\u167F\u1681–\u180D\u180F–\u1DBF]/, - /[\u1E00–\u1FFF]/, - /[\u200B–\u200D\u202A–\u202E\u203F–\u2040\u2054\u2060–\u206F]/, - /[\u2070–\u20CF\u2100–\u218F\u2460–\u24FF\u2776–\u2793]/, - /[\u2C00–\u2DFF\u2E80–\u2FFF]/, - /[\u3004–\u3007\u3021–\u302F\u3031–\u303F\u3040–\uD7FF]/, - /[\uF900–\uFD3D\uFD40–\uFDCF\uFDF0–\uFE1F\uFE30–\uFE44]/, - /[\uFE47–\uFFFD]/ + /[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/, + /[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/, + /[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/, + /[\u1E00-\u1FFF]/, + /[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/, + /[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/, + /[\u2C00-\u2DFF\u2E80-\u2FFF]/, + /[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/, + /[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/, + /[\uFE47-\uFFFD]/ // 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, - // /[\u{90000}–\u{9FFFD}\u{A0000–\u{AFFFD}\u{B0000}–\u{BFFFD}\u{C0000}–\u{CFFFD}]/u, - // /[\u{D0000}–\u{DFFFD}\u{E0000–\u{EFFFD}]/u + // /[\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, + // /[\u{90000}-\u{9FFFD}\u{A0000-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}]/u, + // /[\u{D0000}-\u{DFFFD}\u{E0000-\u{EFFFD}]/u ); +// Valid operator. +export const operator = concat(operatorHead, operatorCharacter, '*'); + // Valid characters for identifiers. export const identifierCharacter = either( identifierHead, /\d/, - /[\u0300–\u036F\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F]/ + /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/ ); // Valid identifier. diff --git a/src/languages/swift.js b/src/languages/swift.js index 3a6f204ee6..0a92edf31e 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -10,7 +10,8 @@ Category: common, system import * as Swift from './lib/swift.js'; import { concat, - either + either, + lookahead } from '../lib/regex.js'; /** @type LanguageFn */ @@ -18,17 +19,10 @@ export default function(hljs) { // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413 // https://docs.swift.org/swift-book/ReferenceManual/zzSummaryOfTheGrammar.html const DOT_KEYWORD = { - begin: concat(/\./, either(...Swift.dotKeywords, ...Swift.optionalDotKeywords)), - returnBegin: true, - contains: [ - { - begin: /\./ - }, - { - className: 'keyword', - begin: either(...Swift.dotKeywords, ...Swift.optionalDotKeywords) - } - ] + className: 'keyword', + begin: concat(/\./, lookahead(either(...Swift.dotKeywords, ...Swift.optionalDotKeywords))), + end: either(...Swift.dotKeywords, ...Swift.optionalDotKeywords), + excludeBegin: true }; const KEYWORD_GUARD = { // Consume .keyword to prevent highlighting properties and methods as keywords. @@ -80,9 +74,10 @@ export default function(hljs) { // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418 const OPERATOR = { className: 'operator', + relevance: 0, variants: [ { - begin: `${Swift.operatorHead}${Swift.operatorCharacter}*` + begin: Swift.operator }, { begin: `\\.(\\.|${Swift.operatorCharacter})+` @@ -189,9 +184,9 @@ export default function(hljs) { ]; // https://docs.swift.org/swift-book/ReferenceManual/Attributes.html - const AVAILABLE = { - begin: /(@|#)available/, - returnBegin: true, + const AVAILABLE_ATTRIBUTE = { + begin: lookahead(/(@|#)available/), // also matches #available + relevance: 0, contains: [ { className: 'keyword', @@ -200,7 +195,6 @@ export default function(hljs) { { begin: /\(/, end: /\)/, - endsParent: true, keywords: Swift.availabilityKeywords.join(' '), contains: [ OPERATOR, @@ -219,7 +213,7 @@ export default function(hljs) { begin: concat(/@/, Swift.identifier) }; const ATTRIBUTES = [ - AVAILABLE, + AVAILABLE_ATTRIBUTE, KEYWORD_ATTRIBUTE, USER_DEFINED_ATTRIBUTE ]; @@ -232,7 +226,8 @@ export default function(hljs) { // slightly more special to swift const OPTIONAL_USING_TYPE = { className: 'type', - begin: '\\b[A-Z][\\w\u00C0-\u02B8\']*[!?]' + begin: '\\b[A-Z][\\w\u00C0-\u02B8\']*[!?]', + relevance: 0 }; const BLOCK_COMMENT = hljs.COMMENT( '/\\*', From b5e27675b5374913ed7c11ef049ed5dabd380642 Mon Sep 17 00:00:00 2001 From: Steven Van Impe Date: Sat, 12 Dec 2020 18:19:20 +0100 Subject: [PATCH 11/17] Small move --- src/languages/lib/swift.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/languages/lib/swift.js b/src/languages/lib/swift.js index 4a988a4890..d08b49da93 100644 --- a/src/languages/lib/swift.js +++ b/src/languages/lib/swift.js @@ -219,6 +219,9 @@ export const operatorCharacter = either( // /[\u{E0100}-\u{E01EF}]/u ); +// Valid operator. +export const operator = concat(operatorHead, operatorCharacter, '*'); + // Valid first characters for identifiers. export const identifierHead = either( /[a-zA-Z_]/, @@ -239,9 +242,6 @@ export const identifierHead = either( // /[\u{D0000}-\u{DFFFD}\u{E0000-\u{EFFFD}]/u ); -// Valid operator. -export const operator = concat(operatorHead, operatorCharacter, '*'); - // Valid characters for identifiers. export const identifierCharacter = either( identifierHead, From 71ce42e5ff56b7489bc3636c82402de7ba5be187 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sun, 13 Dec 2020 15:00:30 -0500 Subject: [PATCH 12/17] explain dot operators --- src/languages/swift.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languages/swift.js b/src/languages/swift.js index 0a92edf31e..c5cde2444b 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -80,6 +80,9 @@ export default function(hljs) { begin: 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})+` } ] From d249413ae830c17a68e01001cb08ff019fb786a6 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Tue, 15 Dec 2020 10:49:42 -0500 Subject: [PATCH 13/17] use built in keywords functionality --- src/languages/lib/swift.js | 84 ++++++++++++++++++++------------------ src/languages/swift.js | 51 +++++++++++++---------- 2 files changed, 75 insertions(+), 60 deletions(-) diff --git a/src/languages/lib/swift.js b/src/languages/lib/swift.js index d08b49da93..f06a7104dc 100644 --- a/src/languages/lib/swift.js +++ b/src/languages/lib/swift.js @@ -1,10 +1,9 @@ -/* eslint-disable no-misleading-character-class */ import { concat, either } from '../../lib/regex.js'; -const keywordWrapper = keyword => concat( +export const keywordWrapper = keyword => concat( /\b/, keyword, /\w$/.test(keyword) ? /\b/ : /\B/ @@ -22,13 +21,20 @@ export const optionalDotKeywords = [ 'self' ].map(keywordWrapper); +// should register as keyword, not type +export const keywordTypes = [ + 'Any', + 'Self' +]; + // Regular keywords and literals. export const keywords = [ - 'Any', - 'Self', + // strings below will be fed into the regular `keywords` engine while regex + // will result in additional modes being created to scan for those keywords to + // avoid conflicts with other rules 'associatedtype', - 'as\\?', // operator - 'as!', // operator + /as\?/, // operator + /as!/, // operator 'as', // operator 'break', 'case', @@ -46,7 +52,7 @@ export const keywords = [ 'enum', 'extension', 'fallthrough', - 'fileprivate\\(set\\)', + 'fileprivate(set)', 'fileprivate', 'final', // contextual 'for', @@ -57,10 +63,10 @@ export const keywords = [ 'import', 'indirect', // contextual 'infix', // contextual - 'init\\?', - 'init!', + /init\?/, + /init!/, 'inout', - 'internal\\(set\\)', + 'internal(set)', 'internal', 'in', 'is', // operator @@ -68,7 +74,7 @@ export const keywords = [ 'let', 'mutating', // contextual 'nonmutating', // contextual - 'open\\(set\\)', // contextual + 'open(set)', // contextual 'open', // contextual 'operator', 'optional', // contextual @@ -76,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 @@ -94,19 +100,19 @@ export const keywords = [ 'switch', 'throws', 'throw', - 'try\\?', // operator - 'try!', // operator + /try\?/, // operator + /try!/, // operator 'try', // operator 'typealias', - 'unowned\\(safe\\)', // contextual - 'unowned\\(unsafe\\)', // contextual + 'unowned(safe)', // contextual + 'unowned(unsafe)', // contextual 'unowned', // contextual 'var', 'weak', // contextual 'where', 'while', 'willSet' // contextual -].map(keywordWrapper); +]; // NOTE: Contextual keywords are reserved only in specific contexts. // Ideally, these should be matched using modes to avoid false positives. @@ -120,31 +126,31 @@ export const literals = [ 'false', 'nil', 'true' -].map(keywordWrapper); +]; // Keywords that start with a number sign (#). // #available is handled separately. export const numberSignKeywords = [ - 'colorLiteral', - 'column', - 'dsohandle', - 'else', - 'elseif', - 'endif', - 'error', - 'file', - 'fileID', - 'fileLiteral', - 'filePath', - 'function', - 'if', - 'imageLiteral', - 'keyPath', - 'line', - 'selector', - 'sourceLocation', - 'warn_unqualified_access', - 'warning' + '#colorLiteral', + '#column', + '#dsohandle', + '#else', + '#elseif', + '#endif', + '#error', + '#file', + '#fileID', + '#fileLiteral', + '#filePath', + '#function', + '#if', + '#imageLiteral', + '#keyPath', + '#line', + '#selector', + '#sourceLocation', + '#warn_unqualified_access', + '#warning' ]; // Global functions in the Standard Library. diff --git a/src/languages/swift.js b/src/languages/swift.js index c5cde2444b..546f3a7cda 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -29,28 +29,33 @@ export default function(hljs) { begin: concat(/\./, either(...Swift.keywords)), relevance: 0 }; + const PLAIN_KEYWORDS = Swift.keywords + .filter(kw => typeof kw === 'string') + .concat([ "_|0" ]); // seems common, so 0 relevance + const REGEX_KEYWORDS = Swift.keywords + .filter(kw => typeof kw !== 'string') // find regex + .concat(Swift.keywordTypes) + .map(Swift.keywordWrapper); const KEYWORD = { variants: [ { className: 'keyword', - begin: either(...Swift.keywords, ...Swift.optionalDotKeywords) - }, - { - className: 'literal', - begin: either(...Swift.literals) - }, - { - className: 'keyword', - begin: /\b_\b/, - relevance: 0 - }, - { - className: 'keyword', - begin: concat(/#/, either(...Swift.numberSignKeywords)) + begin: either(...REGEX_KEYWORDS, ...Swift.optionalDotKeywords) } ] }; - const KEYWORDS = [ + // find all the regular keywords + const KEYWORDS = { + $pattern: either( + /\b\w+(\(\w+\))?\??/, // kw or kw(arg) + /#\w+/ // number keywords + ), + keyword: PLAIN_KEYWORDS + .concat(Swift.numberSignKeywords) + .join(" "), + literal: Swift.literals.join(" ") + }; + const KEYWORD_MODES = [ DOT_KEYWORD, KEYWORD_GUARD, KEYWORD @@ -80,8 +85,8 @@ export default function(hljs) { begin: 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 + // 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})+` } @@ -244,8 +249,9 @@ export default function(hljs) { for (const variant of STRING.variants) { const interpolation = variant.contains.find(mode => mode.label === "interpol"); // TODO: Interpolation can contain any expression, so there's room for improvement here. + interpolation.keywords = KEYWORDS; const submodes = [ - ...KEYWORDS, + ...KEYWORD_MODES, ...BUILT_INS, OPERATOR, NUMBER, @@ -267,6 +273,7 @@ export default function(hljs) { return { name: 'Swift', + keywords: KEYWORDS, contains: [ hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT, @@ -288,9 +295,10 @@ export default function(hljs) { begin: /\(/, end: /\)/, endsParent: true, + keywords: KEYWORDS, contains: [ 'self', - ...KEYWORDS, + ...KEYWORD_MODES, NUMBER, STRING, hljs.C_BLOCK_COMMENT_MODE, @@ -308,11 +316,12 @@ export default function(hljs) { beginKeywords: 'struct protocol class extension enum', end: '\\{', excludeEnd: true, + keywords: KEYWORDS + ' struct protocol class extension enum', contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/ }), - ...KEYWORDS + ...KEYWORD_MODES ] }, { @@ -324,7 +333,7 @@ export default function(hljs) { ], relevance: 0 }, - ...KEYWORDS, + ...KEYWORD_MODES, ...BUILT_INS, OPERATOR, NUMBER, From 0d679d10fb0e363437d9fbd11b51a69ca22b41c9 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Tue, 15 Dec 2020 10:50:19 -0500 Subject: [PATCH 14/17] avoid two files with same name --- src/languages/lib/{swift.js => kws_swift.js} | 0 src/languages/swift.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/languages/lib/{swift.js => kws_swift.js} (100%) diff --git a/src/languages/lib/swift.js b/src/languages/lib/kws_swift.js similarity index 100% rename from src/languages/lib/swift.js rename to src/languages/lib/kws_swift.js diff --git a/src/languages/swift.js b/src/languages/swift.js index 546f3a7cda..494aee2135 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -7,7 +7,7 @@ Website: https://swift.org Category: common, system */ -import * as Swift from './lib/swift.js'; +import * as Swift from './lib/kws_swift.js'; import { concat, either, From f5f30431274e45327c3309d58142c5e9d077392f Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Tue, 15 Dec 2020 10:52:34 -0500 Subject: [PATCH 15/17] remove kw? case as it is handled by modes --- src/languages/swift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/swift.js b/src/languages/swift.js index 494aee2135..274ce231f5 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -47,7 +47,7 @@ export default function(hljs) { // find all the regular keywords const KEYWORDS = { $pattern: either( - /\b\w+(\(\w+\))?\??/, // kw or kw(arg) + /\b\w+(\(\w+\))?/, // kw or kw(arg) /#\w+/ // number keywords ), keyword: PLAIN_KEYWORDS From 1e2c56dfda32cbb39713647616dd53037f4afbf2 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Tue, 15 Dec 2020 10:59:08 -0500 Subject: [PATCH 16/17] simplify available rule --- src/languages/swift.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/languages/swift.js b/src/languages/swift.js index 274ce231f5..e3812c0890 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -193,23 +193,21 @@ export default function(hljs) { // https://docs.swift.org/swift-book/ReferenceManual/Attributes.html const AVAILABLE_ATTRIBUTE = { - begin: lookahead(/(@|#)available/), // also matches #available - relevance: 0, + begin: /(@|#)available\(/, + end: /\)/, + keywords: { + $pattern: /[@#]?\w+/, + keyword: Swift.availabilityKeywords + .concat([ + "@available", + "#available" + ]) + .join(' ') + }, contains: [ - { - className: 'keyword', - begin: /(@|#)available/ - }, - { - begin: /\(/, - end: /\)/, - keywords: Swift.availabilityKeywords.join(' '), - contains: [ - OPERATOR, - NUMBER, - STRING - ] - } + OPERATOR, + NUMBER, + STRING ] }; const KEYWORD_ATTRIBUTE = { From 1e55113525ef0965b0c930c20ae60a09fea3bada Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Tue, 15 Dec 2020 11:07:34 -0500 Subject: [PATCH 17/17] existing keywords are sufficient --- src/languages/swift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/swift.js b/src/languages/swift.js index e3812c0890..84c9f12f92 100644 --- a/src/languages/swift.js +++ b/src/languages/swift.js @@ -314,7 +314,7 @@ export default function(hljs) { beginKeywords: 'struct protocol class extension enum', end: '\\{', excludeEnd: true, - keywords: KEYWORDS + ' struct protocol class extension enum', + keywords: KEYWORDS, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/