Last active
September 6, 2016 01:57
-
-
Save erica/e9d8b541c3f08814ac0e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma mark - Delintage | |
- (void) lint: (NSString *) path | |
{ | |
NSString *string = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; | |
if (!string) return; | |
NSArray *lines = [string componentsSeparatedByString:@"\n"]; | |
int count = 0; | |
int cautions = 0; | |
int warnings = 0; | |
// In shell build phases you can write to stderr using the following format: | |
// <filename>:<linenumber>: error | warn | note : <message>\n | |
for (NSString *eachLine in lines) | |
{ | |
++count; | |
NSString *line = eachLine; | |
// META PROCESSING | |
// No worries, mate! Skip any line with nwm | |
if ([RegexHelper testString:@"nwm" inString:line]) continue; | |
// Convert all FIXMEs to warnings | |
if ([RegexHelper testString:@"FIXME" inString:line]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd is broken", path, count, count); | |
} | |
else if ([RegexHelper testString:@"NOTE: " inString:line]) | |
{ | |
NSLog(@"NOTE"); | |
NSRange range = [line rangeOfString:@"NOTE: "]; // should always be found | |
NSString *remaining = [line substringFromIndex:range.location]; | |
Log(@"%@:%zd: note: Line %zd : %@", path, count, count, remaining); | |
} | |
else if ([RegexHelper testString:@"WARNING: " inString:line]) | |
{ | |
NSLog(@"WARNING"); | |
NSRange range = [line rangeOfString:@"WARNING: "]; // should always be found | |
NSString *remaining = [line substringFromIndex:range.location]; | |
Log(@"%@:%zd: warning: Line %zd : %@", path, count, count, remaining); | |
} | |
else if ([RegexHelper testString:@"ERROR: " inString:line]) | |
{ | |
NSLog(@"ERROR"); | |
NSRange range = [line rangeOfString:@"ERROR: "]; // should always be found | |
NSString *remaining = [line substringFromIndex:range.location]; | |
Log(@"%@:%zd: error: Line %zd : %@", path, count, count, remaining); | |
encounteredErrors = YES; | |
} | |
// AVOID FALSE PINGS ON COMMENTED LINES | |
// Clip off trailing comments | |
NSRange range = [line rangeOfString:@"// "]; | |
if (range.location != NSNotFound) | |
{ | |
line = [line substringToIndex:range.location]; | |
// Also trims start of line, but that shouldn't be an issue for the following checks | |
line = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; | |
} | |
// STYLE ISSUES | |
if (!skipStyleChecks) | |
{ | |
// Handle colons | |
if ([RegexHelper testPattern:@"\\[\\S+\\s*:\\s+\\S+\\]" inString:line] || | |
[RegexHelper testPattern:@"\\[\\S+\\s*:\\s+\\S+\\," inString:line]) | |
// [RegexHelper testPattern:@",\\s+\\S+\\s*:\\s+\\S+\\]" inString:line]) | |
{ | |
// Tightening for dicts. Incomplete but enough to get you to the right lines | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd uses loose spacing for likely Dictionary type declaration", path, count, count); | |
} | |
else if ([RegexHelper testPattern:@"\\?\\s+\\S+\\s+:" inString:line]) | |
{ | |
// ignore ? x : y in ternary | |
} | |
else if ([RegexHelper testPattern:@"\\?\\s+:" inString:line]) | |
{ | |
// ignore ? : | |
} | |
else if ([RegexHelper testPattern:@"\\s+\\:" inString:line]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd uses a space before a colon", path, count, count); | |
} | |
// Too much content in single-line scope -- allow adjustment? min size selection? | |
// if ([RegexHelper testPattern:@"\\{.{8,}\\}" inString:line]) | |
// { | |
// warnings++; | |
// Log(@"%@:%zd: warning: Excessive content in single-line scope", path, count, count); | |
// } | |
if ([RegexHelper testPattern:@"\\{.*;.*\\}" inString:line]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Excessive content in single-line scope", path, count, count); | |
} | |
// Allman | |
if ([RegexHelper testPattern:@"^\\s*\\{" inString:line]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd uses egregious Allman pattern. Welcome to Swift.", path, count, count); | |
} | |
} | |
// FILE HYGIENE | |
if (!skipHygieneChecks) | |
{ | |
if ([RegexHelper testPattern:@"\\s+//.*\\S\\s+$" inString:line]) | |
{ | |
// ignore extra spaces on comment lines | |
} | |
else if ([RegexHelper testPattern:@"\\S\\s+$" inString:line]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd includes trailing whitespace characters", path, count, count); | |
} | |
} | |
// LANGUAGE ISSUES | |
// ; at the ends of lines | |
if ([RegexHelper testPattern:@";\\s*$" inString:line]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd: Swift does not require terminal semicolons except for statement separation", path, count, count); | |
} | |
// Disabled because tuples | |
// return ( | |
// if ([RegexHelper testPattern:@"return[\\s]*\\([^\\)]*\\).*\\w" inString:line]) | |
// { | |
// // ignore return types where text follows closing parens | |
// /* | |
// return (3 + 5) - should fail | |
// return (foo(a)) - should fail | |
// return (foo(a) + b) - should fail but will not with this pattern | |
// return (a.x + b.x) / (a.y * b.y) - should succeed | |
// return (a.x + b.x) / 5 - should succeed | |
// */ | |
// } | |
// else if ([RegexHelper testPattern:@"return[\\s]*\\([^\\)]" inString:line]) | |
// { | |
// warnings++; | |
// Log(@"%@:%zd: warning: Line %zd: Swift return statements do not require parentheses", path, count, count); | |
// } | |
// if ( | |
if ([RegexHelper testPattern:@"if[\\s]*\\(" inString:line]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd: Swift if statements do not require parentheses", path, count, count); | |
} | |
// Extraneous lets | |
// For multi-line in-context scan, would test for ,\s*\n\s*let | |
if ([RegexHelper testString:@"(let" inString:line]) | |
{ | |
// Skip lines that are likely tuple assignments in switch statements | |
} | |
else if ([RegexHelper testString:@", let" inString:line]) | |
{ | |
cautions++; | |
Log(@"%@:%zd: note: Line %zd: CAUTION: Check for extraneous let usage in cascaded let", path, count, count); | |
} | |
if ([RegexHelper testString:@"as!" inString:line]) | |
{ | |
cautions++; | |
Log(@"%@:%zd: note: Line %zd: CAUTION: Forced casts are generally unsafe", path, count, count); | |
} | |
// Breaks in switch patterns other than default | |
// For multi-line in-context, should test for control-flow use of break | |
if ([RegexHelper testPattern:@":\\s+break" inString:line]) | |
{ | |
// skip : break cases | |
} | |
else if ([RegexHelper testString:@"break" inString:line]) | |
{ | |
// was the previous line "default"? | |
// this line is count - 1. previous line is count - 2 | |
if ((count - 2) > 0) | |
{ | |
NSString *previousLine = lines[count - 2]; | |
if (![RegexHelper testString:@"default:" inString:previousLine]) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd: Swift cases do not implicitly fall through.", path, count, count); | |
} | |
} | |
} | |
// Enumeration prefixes | |
for (NSString *prefix in prefixes) | |
{ | |
// Has enumeration prefix with dot after but no rawValue | |
if ([line rangeOfString:[prefix stringByAppendingString:@"."]].location != NSNotFound && [line rangeOfString:@"rawValue"].location == NSNotFound | |
) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd: Swift type inference may not require enumeration prefix on this line", path, count, count); | |
} | |
} | |
// Constructors | |
BOOL testForInferredConstructors = YES; | |
if (testForInferredConstructors) | |
{ | |
// Matches most no-arg class methods as likely constructors | |
if ([RegexHelper testString:@"\\W[A-Z]\\w+\\.\\w+\\(\\)" inString:line]) | |
{ | |
cautions++; | |
Log(@"%@:%zd: note: Line %zd: CAUTION: Likely constructor pattern may not require inferred prefix", path, count, count); | |
} | |
} | |
// Self references | |
BOOL treatSelfRefAsWarning = YES; | |
if ([RegexHelper testString:@"self.init" inString:line]) | |
{ | |
// Skip self.init pattern | |
} | |
else if ([RegexHelper testPattern:@"self\\.(\\w+)\\s*=\\s*\\1" inString:line]) | |
{ | |
// Skip likely self-initialization self.x = x | |
} | |
else if ([RegexHelper testPattern:@"\\\\\\(self" inString:line]) | |
{ | |
// Skip likely in-string reference \(self | |
} | |
else if ([RegexHelper testPattern:@"self\\.(\\S)+\\s*=\\s*\\S" inString:line]) | |
{ | |
// initialization | |
if (treatSelfRefAsWarning) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd: Swift does not usually require 'self' references for assignments", path, count, count); | |
} | |
else | |
{ | |
cautions++; | |
Log(@"%@:%zd: note: Line %zd: CAUTION: Swift does not usually require 'self' references for assignments", path, count, count); | |
} | |
} | |
else if ([RegexHelper testString:@"self." inString:line]) | |
{ | |
if (treatSelfRefAsWarning) | |
{ | |
warnings++; | |
Log(@"%@:%zd: warning: Line %zd: Swift does not usually require 'self' references outside of closures", path, count, count); | |
} | |
else | |
{ | |
cautions++; | |
Log(@"%@:%zd: note: Line %zd: CAUTION: Swift does not usually require 'self' references outside of closures", path, count, count); | |
} | |
} | |
} | |
if (!skipHygieneChecks) | |
{ | |
// File-level line hygiene | |
if ([string hasSuffix:@"\n\n"] || ![string hasSuffix:@"\n"]) | |
{ | |
warnings++; | |
Log(@"%@:0: warning: File %@ should have a single trailing newline", path, path.lastPathComponent); | |
} | |
} | |
Log(@"%zd warnings, %zd cautions for %@", warnings, cautions, path.lastPathComponent); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment