/** * @param {string} value * @returns {RegExp} * */ /** * @param {RegExp | string } re * @returns {string} */ function source(re) { if (!re) return null; if (typeof re === "string") return re; return re.source; } /** * @param {RegExp | string } re * @returns {string} */ function lookahead(re) { return concat('(?=', re, ')'); } /** * @param {...(RegExp | string) } args * @returns {string} */ function concat(...args) { const joined = args.map((x) => source(x)).join(""); return joined; } /** * Any of the passed expresssions may match * * Creates a huge this | this | that | that match * @param {(RegExp | string)[] } args * @returns {string} */ function either(...args) { const joined = '(' + args.map((x) => source(x)).join("|") + ")"; return joined; } const keywordWrapper = keyword => concat( /\b/, keyword, /\w$/.test(keyword) ? /\b/ : /\B/ ); // Keywords that require a leading dot. const dotKeywords = [ 'Protocol', // contextual 'Type' // contextual ].map(keywordWrapper); // Keywords that may have a leading dot. const optionalDotKeywords = [ 'init', 'self' ].map(keywordWrapper); // should register as keyword, not type const keywordTypes = [ 'Any', 'Self' ]; // Regular keywords and literals. const keywords = [ // 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', 'async', 'await', /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 ]; // NOTE: Contextual keywords are reserved only in specific contexts. // Ideally, these should be matched using modes to avoid false positives. // Literals. const literals = [ 'false', 'nil', 'true' ]; // Keywords used in precedence groups. const precedencegroupKeywords = [ 'assignment', 'associativity', 'higherThan', 'left', 'lowerThan', 'none', 'right' ]; // Keywords that start with a number sign (#). // #available is handled separately. const numberSignKeywords = [ '#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. 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. 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. 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 operator. const operator = concat(operatorHead, operatorCharacter, '*'); // Valid first characters for identifiers. 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-\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, // /[\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. const identifierCharacter = either( identifierHead, /\d/, /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/ ); // Valid identifier. const identifier = concat(identifierHead, identifierCharacter, '*'); // Valid type identifier. const typeIdentifier = concat(/[A-Z]/, identifierCharacter, '*'); // Built-in attributes, which are highlighted as keywords. // @available is handled separately. 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. const availabilityKeywords = [ 'iOS', 'iOSApplicationExtension', 'macOS', 'macOSApplicationExtension', 'macCatalyst', 'macCatalystApplicationExtension', 'watchOS', 'watchOSApplicationExtension', 'tvOS', 'tvOSApplicationExtension', 'swift' ]; /* Language: Swift Description: Swift is a general-purpose programming language built using a modern approach to safety, performance, and software design patterns. Author: Steven Van Impe Contributors: Chris Eidhof , Nate Cook , Alexander Lichter , Richard Gibson Website: https://swift.org Category: common, system */ /** @type LanguageFn */ function swift(hljs) { const WHITESPACE = { match: /\s+/, relevance: 0 }; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID411 const BLOCK_COMMENT = hljs.COMMENT( '/\\*', '\\*/', { 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 const DOT_KEYWORD = { className: 'keyword', begin: concat(/\./, lookahead(either(...dotKeywords, ...optionalDotKeywords))), end: either(...dotKeywords, ...optionalDotKeywords), excludeBegin: true }; const KEYWORD_GUARD = { // Consume .keyword to prevent highlighting properties and methods as keywords. match: concat(/\./, either(...keywords)), relevance: 0 }; const PLAIN_KEYWORDS = keywords .filter(kw => typeof kw === 'string') .concat([ "_|0" ]); // seems common, so 0 relevance const REGEX_KEYWORDS = keywords .filter(kw => typeof kw !== 'string') // find regex .concat(keywordTypes) .map(keywordWrapper); const KEYWORD = { variants: [ { className: 'keyword', match: either(...REGEX_KEYWORDS, ...optionalDotKeywords) } ] }; // find all the regular keywords const KEYWORDS = { $pattern: either( /\b\w+/, // regular keywords /#\w+/ // number keywords ), keyword: PLAIN_KEYWORDS .concat(numberSignKeywords), literal: literals }; const KEYWORD_MODES = [ 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. match: concat(/\./, either(...builtIns)), relevance: 0 }; const BUILT_IN = { className: 'built_in', match: concat(/\b/, either(...builtIns), /(?=\()/) }; const BUILT_INS = [ BUILT_IN_GUARD, BUILT_IN ]; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418 const OPERATOR_GUARD = { // Prevent -> from being highlighting as an operator. match: /->/, relevance: 0 }; const OPERATOR = { className: 'operator', relevance: 0, variants: [ { match: 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. match: `\\.(\\.|${operatorCharacter})+` } ] }; const OPERATORS = [ OPERATOR_GUARD, OPERATOR ]; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#grammar_numeric-literal // TODO: Update for leading `-` after lookbehind is supported everywhere const decimalDigits = '([0-9]_*)+'; const hexDigits = '([0-9a-fA-F]_*)+'; const NUMBER = { className: 'number', relevance: 0, variants: [ // decimal floating-point-literal (subsumes decimal-literal) { match: `\\b(${decimalDigits})(\\.(${decimalDigits}))?` + `([eE][+-]?(${decimalDigits}))?\\b` }, // hexadecimal floating-point-literal (subsumes hexadecimal-literal) { match: `\\b0x(${hexDigits})(\\.(${hexDigits}))?` + `([pP][+-]?(${decimalDigits}))?\\b` }, // octal-literal { match: /\b0o([0-7]_*)+\b/ }, // binary-literal { match: /\b0b([01]_*)+\b/ } ] }; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#grammar_string-literal const ESCAPED_CHARACTER = (rawDelimiter = "") => ({ className: 'subst', variants: [ { match: concat(/\\/, rawDelimiter, /[0\\tnr"']/) }, { match: concat(/\\/, rawDelimiter, /u\{[0-9a-fA-F]{1,8}\}/) } ] }); const ESCAPED_NEWLINE = (rawDelimiter = "") => ({ className: 'subst', match: concat(/\\/, rawDelimiter, /[\t ]*(?:[\r\n]|\r\n)/) }); const INTERPOLATION = (rawDelimiter = "") => ({ className: 'subst', label: "interpol", begin: concat(/\\/, rawDelimiter, /\(/), end: /\)/ }); const MULTILINE_STRING = (rawDelimiter = "") => ({ begin: concat(rawDelimiter, /"""/), end: concat(/"""/, 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) ] }); const STRING = { className: 'string', variants: [ MULTILINE_STRING(), MULTILINE_STRING("#"), MULTILINE_STRING("##"), MULTILINE_STRING("###"), SINGLE_LINE_STRING(), SINGLE_LINE_STRING("#"), SINGLE_LINE_STRING("##"), SINGLE_LINE_STRING("###") ] }; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID412 const QUOTED_IDENTIFIER = { match: concat(/`/, identifier, /`/) }; const IMPLICIT_PARAMETER = { className: 'variable', match: /\$\d+/ }; const PROPERTY_WRAPPER_PROJECTION = { className: 'variable', match: `\\$${identifierCharacter}+` }; const IDENTIFIERS = [ QUOTED_IDENTIFIER, IMPLICIT_PARAMETER, PROPERTY_WRAPPER_PROJECTION ]; // https://docs.swift.org/swift-book/ReferenceManual/Attributes.html const AVAILABLE_ATTRIBUTE = { match: /(@|#)available/, className: "keyword", starts: { contains: [ { begin: /\(/, end: /\)/, keywords: availabilityKeywords, contains: [ ...OPERATORS, NUMBER, STRING ] } ] } }; const KEYWORD_ATTRIBUTE = { className: 'keyword', match: concat(/@/, either(...keywordAttributes)) }; const USER_DEFINED_ATTRIBUTE = { className: 'meta', match: concat(/@/, identifier) }; const ATTRIBUTES = [ AVAILABLE_ATTRIBUTE, KEYWORD_ATTRIBUTE, USER_DEFINED_ATTRIBUTE ]; // https://docs.swift.org/swift-book/ReferenceManual/Types.html const TYPE = { match: lookahead(/\b[A-Z]/), relevance: 0, contains: [ { // Common Apple frameworks, for relevance boost className: 'type', match: concat(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/, identifierCharacter, '+') }, { // Type identifier className: 'type', match: typeIdentifier, relevance: 0 }, { // Optional type match: /[?!]+/, relevance: 0 }, { // Variadic parameter match: /\.\.\./, relevance: 0 }, { // Protocol composition match: concat(/\s+&\s+/, lookahead(typeIdentifier)), relevance: 0 } ] }; const GENERIC_ARGUMENTS = { begin: //, keywords: KEYWORDS, contains: [ ...COMMENTS, ...KEYWORD_MODES, ...ATTRIBUTES, OPERATOR_GUARD, TYPE ] }; 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(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, identifier, 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: //, contains: [ ...COMMENTS, TYPE ] }; const FUNCTION_PARAMETER_NAME = { begin: either( lookahead(concat(identifier, /\s*:/)), lookahead(concat(identifier, /\s+/, identifier, /\s*:/)) ), end: /:/, relevance: 0, contains: [ { className: 'keyword', match: /\b_\b/ }, { className: 'params', match: 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: /\[|%/ }; // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID380 const OPERATOR_DECLARATION = { beginKeywords: 'operator', end: hljs.MATCH_NOTHING_RE, contains: [ { className: 'title', match: operator, endsParent: true, relevance: 0 } ] }; // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID550 const PRECEDENCEGROUP = { beginKeywords: 'precedencegroup', end: hljs.MATCH_NOTHING_RE, contains: [ { className: 'title', match: typeIdentifier, relevance: 0 }, { begin: /{/, end: /}/, relevance: 0, endsParent: true, keywords: [ ...precedencegroupKeywords, ...literals ], contains: [ TYPE ] } ] }; // 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. interpolation.keywords = KEYWORDS; const submodes = [ ...KEYWORD_MODES, ...BUILT_INS, ...OPERATORS, NUMBER, STRING, ...IDENTIFIERS ]; interpolation.contains = [ ...submodes, { begin: /\(/, end: /\)/, contains: [ 'self', ...submodes ] } ]; } return { name: 'Swift', keywords: KEYWORDS, contains: [ ...COMMENTS, FUNCTION, INIT_SUBSCRIPT, { className: 'class', beginKeywords: 'struct protocol class extension enum', end: '\\{', excludeEnd: true, keywords: KEYWORDS, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/ }), ...KEYWORD_MODES ] }, OPERATOR_DECLARATION, PRECEDENCEGROUP, { beginKeywords: 'import', end: /$/, contains: [ ...COMMENTS ], relevance: 0 }, ...KEYWORD_MODES, ...BUILT_INS, ...OPERATORS, NUMBER, STRING, ...IDENTIFIERS, ...ATTRIBUTES, TYPE, TUPLE ] }; } module.exports = swift;