-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(swift) Revamped keywords and added support for operators #2908
Changes from 12 commits
60a32b2
5d1e17c
4adf9ed
2bb4994
8a7c3f9
86b7c81
09be110
1026ac0
da01153
2a1163e
f8510bd
b5e2767
71ce42e
d249413
0d679d1
f5f3043
1e2c56d
1e55113
821d247
ea6d8df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,301 @@ | ||
/* eslint-disable no-misleading-character-class */ | ||
import { | ||
concat, | ||
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', // contextual | ||
'Type' // contextual | ||
].map(keywordWrapper); | ||
|
||
// Keywords that may have a leading dot. | ||
export const optionalDotKeywords = [ | ||
'init', | ||
'self' | ||
].map(keywordWrapper); | ||
|
||
// Regular keywords and literals. | ||
export const keywords = [ | ||
'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. | ||
|
||
// 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. | ||
|
||
// Literals. | ||
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' | ||
]; | ||
|
||
// 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 operator. | ||
export const operator = concat(operatorHead, operatorCharacter, '*'); | ||
|
||
// 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]/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm showing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this works and is valid Swift code: infix operator ~︦ // ~ 0xFE26
func ~︦ (lhs: Int, rhs: Int) -> Int {
lhs + rhs
}
1 ~︦ 2 Should I add this as a unit test? I assume this isn't a problem here because there are different rules for the first character of an operator. I'm not familiar with these ranges, but I assume the rules for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It wouldn't hurt. Do you have any idea what that warning is trying to tell us? Can the character also be used to make a compound character and then that doesn't work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This one regex is the only one still being flagged and just that portion of it. So it's unique somehow. |
||
); | ||
|
||
// 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' | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't add these, if there are issues remaining we need to resolve them.