Skip to content

Instantly share code, notes, and snippets.

@erica
Last active September 6, 2016 01:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/e9d8b541c3f08814ac0e to your computer and use it in GitHub Desktop.
Save erica/e9d8b541c3f08814ac0e to your computer and use it in GitHub Desktop.
#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