diff --git a/Application/GBAppledocApplication.m b/Application/GBAppledocApplication.m index dcdf2388..8d0e4a3f 100644 --- a/Application/GBAppledocApplication.m +++ b/Application/GBAppledocApplication.m @@ -55,6 +55,8 @@ static NSString *kGBArgKeepMergedCategoriesSections = @"keep-merged-sections"; static NSString *kGBArgPrefixMergedCategoriesSectionsWithCategoryName = @"prefix-merged-sections"; static NSString *kGBArgUseCodeOrder = @"use-code-order"; +static NSString *kGBArgIgnoreSymbol = @"ignore-symbol"; +static NSString *kGBArgRequireLeaderForLocalCrossRefs = @"require-leader-for-local-crossrefs"; static NSString *kGBArgExplicitCrossRef = @"explicit-crossref"; static NSString *kGBArgCrossRefFormat = @"crossref-format"; @@ -305,6 +307,9 @@ - (void)application:(DDCliApplication *)app willParseOptions:(DDGetoptLongParser { GBNoArg(kGBArgKeepMergedCategoriesSections), 0, DDGetoptNoArgument }, { GBNoArg(kGBArgPrefixMergedCategoriesSectionsWithCategoryName), 0, DDGetoptNoArgument }, { GBNoArg(kGBArgUseCodeOrder), 0, DDGetoptNoArgument }, + { kGBArgIgnoreSymbol, 0, DDGetoptRequiredArgument }, + { kGBArgRequireLeaderForLocalCrossRefs, 0, DDGetoptNoArgument }, + { GBNoArg(kGBArgRequireLeaderForLocalCrossRefs), 0, DDGetoptNoArgument }, { kGBArgWarnOnMissingOutputPath, 0, DDGetoptNoArgument }, { kGBArgWarnOnMissingCompanyIdentifier, 0, DDGetoptNoArgument }, @@ -816,6 +821,11 @@ - (void)setNoKeepMergedSections:(BOOL)value { self.settings.keepMergedCategories - (void)setNoPrefixMergedSections:(BOOL)value { self.settings.prefixMergedCategoriesSectionsWithCategoryName = !value; } - (void)setUseCodeOrder:(BOOL)value { self.settings.useCodeOrder = value; } - (void)setNoUseCodeOrder:(BOOL)value { self.settings.useCodeOrder = !value; } +- (void)setIgnoreSymbol:(NSString *)glob { + [self.settings.ignoredSymbols addObject:glob]; +} +-(void)setRequireLeaderForLocalCrossrefs:(BOOL)value { self.settings.requireLeaderForLocalCrossRefs = value; } +-(void)setNoRequireLeaderForLocalCrossrefs:(BOOL)value { self.settings.requireLeaderForLocalCrossRefs = !value; } - (void)setWarnMissingOutputPath:(BOOL)value { self.settings.warnOnMissingOutputPathArgument = value; } - (void)setWarnMissingCompanyId:(BOOL)value { self.settings.warnOnMissingCompanyIdentifier = value; } @@ -942,6 +952,8 @@ - (void)printSettingsAndArguments:(NSArray *)arguments { ddprintf(@"--%@ = %@\n", kGBArgPrefixMergedCategoriesSectionsWithCategoryName, PRINT_BOOL(self.settings.prefixMergedCategoriesSectionsWithCategoryName)); ddprintf(@"--%@ = %@\n", kGBArgCrossRefFormat, self.settings.commentComponents.crossReferenceMarkersTemplate); ddprintf(@"--%@ = %@\n", kGBArgUseCodeOrder, self.settings.useCodeOrder); + for (NSString *glob in self.settings.ignoredSymbols) ddprintf(@"--%@ = %@\n", kGBArgIgnoreSymbol, glob); + ddprintf(@"--%@ = %@\n", kGBArgRequireLeaderForLocalCrossRefs, PRINT_BOOL(self.settings.requireLeaderForLocalCrossRefs)); ddprintf(@"--%@ = %ld\n", kGBArgExitCodeThreshold, self.settings.exitCodeThreshold); ddprintf(@"\n"); @@ -1011,6 +1023,8 @@ - (void)printHelp { PRINT_USAGE(@" ", kGBArgPrefixMergedCategoriesSectionsWithCategoryName, @"", @"[b] Prefix merged sections with category name"); PRINT_USAGE(@" ", kGBArgExplicitCrossRef, @"", @"[b] Shortcut for explicit default cross ref template"); PRINT_USAGE(@" ", kGBArgUseCodeOrder, @"", @"[b] Order sections by the order specified in the input files"); + PRINT_USAGE(@" ", kGBArgIgnoreSymbol, @"", @"[*] A glob to match against symbols. If a symbol matches, it will not be included in the output."); + PRINT_USAGE(@" ", kGBArgRequireLeaderForLocalCrossRefs, @"", @"[b] If true, only auto-link local symbol references that begin with '-' or '+'."); PRINT_USAGE(@" ", kGBArgCrossRefFormat, @"", @"Cross reference template regex"); PRINT_USAGE(@" ", kGBArgExitCodeThreshold, @"", @"Exit code threshold below which 0 is returned"); ddprintf(@"\n"); diff --git a/Application/GBApplicationSettingsProvider.h b/Application/GBApplicationSettingsProvider.h index 2eddfb02..cec72b3c 100644 --- a/Application/GBApplicationSettingsProvider.h +++ b/Application/GBApplicationSettingsProvider.h @@ -386,6 +386,18 @@ NSString *NSStringFromGBPublishedFeedFormats(GBPublishedFeedFormats format); */ @property (assign) BOOL useCodeOrder; +/** A set of globs to be matched against each input symbol to determine whether the symbol should be ignored + + */ +@property (retain) NSMutableSet* ignoredSymbols; + +/** Specifies whether auto-linking for 'local' cross references requires the symbol to begin with '-' or '+' + + If 'YES' then auto-linking of local symbols will only happen if the symbol is written in the comment with a leading '+' or '-'. This reduces false-positives, where links are created out of common words, because there are local symbols named after common words (e.g 'and') + + */ +@property (assign) BOOL requireLeaderForLocalCrossRefs; + /** Indicates whteher local methods and properties cross references texts should be prefixed when used in related items list. If `YES`, instance methods are prefixed with `-`, class methods with `+` and properties with `@property` when used as cross reference in related items list (i.e. see also section for methods). If `NO`, no prefix is used. diff --git a/Application/GBApplicationSettingsProvider.m b/Application/GBApplicationSettingsProvider.m index 12f1a424..38d02807 100644 --- a/Application/GBApplicationSettingsProvider.m +++ b/Application/GBApplicationSettingsProvider.m @@ -146,6 +146,8 @@ - (id)init { self.keepMergedCategoriesSections = NO; self.prefixMergedCategoriesSectionsWithCategoryName = NO; self.useCodeOrder = NO; + self.ignoredSymbols = [ NSMutableSet set ]; + self.requireLeaderForLocalCrossRefs = NO; self.prefixLocalMembersInRelatedItemsList = YES; self.embedCrossReferencesWhenProcessingMarkdown = YES; @@ -679,6 +681,8 @@ - (NSString *)versionIdentifier { @synthesize keepMergedCategoriesSections; @synthesize prefixMergedCategoriesSectionsWithCategoryName; @synthesize useCodeOrder; +@synthesize ignoredSymbols; +@synthesize requireLeaderForLocalCrossRefs; @synthesize prefixLocalMembersInRelatedItemsList; @synthesize embedCrossReferencesWhenProcessingMarkdown; diff --git a/Application/GBCommentComponentsProvider.h b/Application/GBCommentComponentsProvider.h index c7a3ca89..065643d9 100644 --- a/Application/GBCommentComponentsProvider.h +++ b/Application/GBCommentComponentsProvider.h @@ -135,9 +135,10 @@ The result of the method depends on the templated value: if the value is `YES`, the string includes template from `crossReferenceMarkersTemplate`, otherwise it only contains "pure" regex. The first option should be used for in-text cross references detection, while the second for `crossReferenceRegex` matching. @param templated If `YES` templated regex is returned, otherwise pure one. + @param requireLeader If 'YES', the regex will only match local member cross references with a '+' or '-' leader. Making this a requirement will reduce false links, but may lose some valid links. @return Returns the regex used for matching cross reference. */ -- (NSString *)localMemberCrossReferenceRegex:(BOOL)templated; +- (NSString *)localMemberCrossReferenceRegex:(BOOL)templated requireLeader:(BOOL)requireLeader; /** Returns the regex used for matching (possible) category cross reference with capture 1 containing category name. diff --git a/Application/GBCommentComponentsProvider.m b/Application/GBCommentComponentsProvider.m index cb53e917..7f0ac3d3 100644 --- a/Application/GBCommentComponentsProvider.m +++ b/Application/GBCommentComponentsProvider.m @@ -115,11 +115,23 @@ - (NSString *)remoteMemberCrossReferenceRegex:(BOOL)templated { } } -- (NSString *)localMemberCrossReferenceRegex:(BOOL)templated { +- (NSString *)localMemberCrossReferenceRegex:(BOOL)templated requireLeader:(BOOL)requireLeader { if (templated) { - GBRETURN_ON_DEMAND([self crossReferenceRegexForRegex:[self localMemberCrossReferenceRegex:NO]]); + static NSString *regexLeaderRequired = nil; + static NSString *regexLeaderNotRequired = nil; + if (!regexLeaderRequired) { + regexLeaderRequired = [self crossReferenceRegexForRegex:[self localMemberCrossReferenceRegex:NO requireLeader:YES]]; + regexLeaderNotRequired = [ self crossReferenceRegexForRegex:[self localMemberCrossReferenceRegex:NO requireLeader:NO]]; + } + return requireLeader ? regexLeaderRequired : regexLeaderNotRequired; } else { - GBRETURN_ON_DEMAND(@"([+-]?)([^>,.;!?()\\s]+)"); + static NSString *regexLeaderRequired = nil; + static NSString *regexLeaderNotRequired = nil; + if (!regexLeaderRequired) { + regexLeaderRequired = @"([+-])([^>,.;!?()\\s]+)"; + regexLeaderNotRequired = @"([+-]?)([^>,.;!?()\\s]+)"; + } + return requireLeader ? regexLeaderRequired : regexLeaderNotRequired; } } diff --git a/Common/NSString+GBString.h b/Common/NSString+GBString.h index 0a7c7151..df4338c8 100644 --- a/Common/NSString+GBString.h +++ b/Common/NSString+GBString.h @@ -109,4 +109,12 @@ */ - (NSUInteger)numberOfLinesInRange:(NSRange)range; + +/** Glob matching + + @param glob The glob to match against the receiver. '*' matches any number of characters, '?' matches any one character + @return Returns whether glob matches the receiver + */ +- (BOOL)matchesGlob:(NSString *)glob; + @end diff --git a/Common/NSString+GBString.m b/Common/NSString+GBString.m index 5ec12d93..4eeddf64 100644 --- a/Common/NSString+GBString.m +++ b/Common/NSString+GBString.m @@ -126,6 +126,50 @@ - (NSUInteger)numberOfLinesInRange:(NSRange)range { return [lines count]; } +- (BOOL)matchesGlob:(NSString *)glob +{ + NSUInteger strIdxForCurrentStarMatch = 0, afterLastStarIdx = NSNotFound; + NSUInteger strIdx = 0, globIdx = 0; + NSUInteger strLen = self.length; + NSUInteger globLen = glob.length; + + while (strIdx < strLen && ([glob characterAtIndex:globIdx] != '*')) { + if (([glob characterAtIndex:globIdx] != [self characterAtIndex:strIdx]) && + ([glob characterAtIndex:globIdx] != '?')) { + return NO; + } + globIdx++; + strIdx++; + } + + while (strIdx < strLen) { + if ([glob characterAtIndex:globIdx] == '*') { + globIdx++; + if (globIdx == globLen) { + return YES; + } + afterLastStarIdx = globIdx; + strIdxForCurrentStarMatch = strIdx + 1; + } + else if (([glob characterAtIndex:globIdx] == [self characterAtIndex:strIdx]) || + ([glob characterAtIndex:globIdx] == '?')) { + globIdx++; + strIdx++; + } + else if (afterLastStarIdx != NSNotFound) { + globIdx = afterLastStarIdx; + strIdx = strIdxForCurrentStarMatch++; + } + else { + return NO; + } + } + + while (globIdx < globLen && [glob characterAtIndex:globIdx] == '*') { + globIdx++; + } + return (globIdx == globLen); +} @end #pragma mark - diff --git a/Parsing/GBObjectiveCParser.m b/Parsing/GBObjectiveCParser.m index a9ede3ca..24c0b319 100644 --- a/Parsing/GBObjectiveCParser.m +++ b/Parsing/GBObjectiveCParser.m @@ -29,6 +29,7 @@ - (void)updateLastComment:(GBComment **)comment sectionComment:(GBComment **)sec @interface GBObjectiveCParser (DefinitionParsing) +- (BOOL)isIgnoredSymbol:(NSString*)symbol; - (void)matchClassDefinition; - (void)matchCategoryDefinition; - (void)matchExtensionDefinition; @@ -150,9 +151,24 @@ - (void)updateLastComment:(GBComment **)comment sectionComment:(GBComment **)sec @implementation GBObjectiveCParser (DefinitionParsing) +- (BOOL)isIgnoredSymbol:(NSString*)symbol { + for (NSString* glob in settings.ignoredSymbols) { + if ([symbol matchesGlob:glob]) { + return YES; + } + } + return NO; +} + - (void)matchClassDefinition { // @interface CLASSNAME NSString *className = [[self.tokenizer lookahead:1] stringValue]; + if ([self isIgnoredSymbol:className]) { + GBLogInfo(@"Skipping ignored class %@", className); + [self.tokenizer consumeTo:@"@end" usingBlock:^(PKToken *token, BOOL *consume, BOOL *stop) { }]; + return; + } + GBClassData *class = [GBClassData classDataWithName:className]; class.includeInOutput = self.includeInOutput; [self registerSourceInfoFromCurrentTokenToObject:class]; @@ -174,6 +190,12 @@ - (void)matchCategoryDefinition { // @interface CLASSNAME ( CATEGORYNAME ) NSString *className = [[self.tokenizer lookahead:1] stringValue]; NSString *categoryName = [[self.tokenizer lookahead:3] stringValue]; + NSString* fullName = [NSString stringWithFormat:@"%@(%@)",className,categoryName]; + if ([self isIgnoredSymbol:fullName]) { + GBLogInfo(@"Skipping ignored category %@", fullName); + [self.tokenizer consumeTo:@"@end" usingBlock:^(PKToken *token, BOOL *consume, BOOL *stop) { }]; + return; + } GBCategoryData *category = [GBCategoryData categoryDataWithName:categoryName className:className]; category.includeInOutput = self.includeInOutput; [self registerSourceInfoFromCurrentTokenToObject:category]; @@ -192,6 +214,11 @@ - (void)matchCategoryDefinition { - (void)matchExtensionDefinition { // @interface CLASSNAME ( ) NSString *className = [[self.tokenizer lookahead:1] stringValue]; + if ([self isIgnoredSymbol:className]) { + GBLogInfo(@"Skipping ignored class extension %@", className); + [self.tokenizer consumeTo:@"@end" usingBlock:^(PKToken *token, BOOL *consume, BOOL *stop) { }]; + return; + } GBCategoryData *extension = [GBCategoryData categoryDataWithName:nil className:className]; extension.includeInOutput = self.includeInOutput; GBLogVerbose(@"Matched %@() extension definition at line %lu.", className, extension.prefferedSourceInfo.lineNumber); @@ -210,6 +237,11 @@ - (void)matchExtensionDefinition { - (void)matchProtocolDefinition { // @protocol PROTOCOLNAME NSString *protocolName = [[self.tokenizer lookahead:1] stringValue]; + if ([self isIgnoredSymbol:protocolName]) { + GBLogInfo(@"Skipping ignored protocol %@", protocolName); + [self.tokenizer consumeTo:@"@end" usingBlock:^(PKToken *token, BOOL *consume, BOOL *stop) { }]; + return; + } GBProtocolData *protocol = [GBProtocolData protocolDataWithName:protocolName]; protocol.includeInOutput = self.includeInOutput; GBLogVerbose(@"Matched %@ protocol definition at line %lu.", protocolName, protocol.prefferedSourceInfo.lineNumber); diff --git a/Processing/GBCommentsProcessor.m b/Processing/GBCommentsProcessor.m index 988b7a12..8cd6a9ea 100644 --- a/Processing/GBCommentsProcessor.m +++ b/Processing/GBCommentsProcessor.m @@ -950,7 +950,7 @@ - (GBCrossRefData *)dataForLocalMemberLinkInString:(NSString *)string searchRang if (!self.currentContext) return nil; BOOL templated = (flags & GBProcessingFlagRelatedItem) == 0; - NSString *regex = [self.components localMemberCrossReferenceRegex:templated]; + NSString *regex = [self.components localMemberCrossReferenceRegex:templated requireLeader:self.settings.requireLeaderForLocalCrossRefs ]; NSArray *components = [string captureComponentsMatchedByRegex:regex range:searchRange]; if ([components count] == 0) return nil; diff --git a/Testing/GBApplicationTesting.m b/Testing/GBApplicationTesting.m index 818c32a2..567492f8 100644 --- a/Testing/GBApplicationTesting.m +++ b/Testing/GBApplicationTesting.m @@ -345,6 +345,25 @@ - (void)testExitCodeThreshold_shouldAssignValueToSettings { assertThatInteger(settings.exitCodeThreshold, equalToInteger(2)); } +- (void)testIgnoreSymbol_shouldAssignValueToSettings { + // setup & execute + GBApplicationSettingsProvider *settings = [self settingsByRunningWithArgs:@"--ignore-symbol", @"*deprecated*", @"--ignore-symbol", @"DummyClass?", nil]; + // verify - note that ignore should not convert dot to current path; this would prevent .m being parsed properly! + assertThatInteger([settings.ignoredSymbols count], equalToInteger(2)); + assertThatBool([settings.ignoredSymbols containsObject:@"*deprecated*"], equalToBool(YES)); + assertThatBool([settings.ignoredSymbols containsObject:@"DummyClass?"], equalToBool(YES)); +} + +- (void)testRequireLeaderForLocalCrossRefs_shouldAssignValueToSettings { + // setup & execute + GBApplicationSettingsProvider *settings1 = [self settingsByRunningWithArgs:@"--require-leader-for-local-crossrefs", nil]; + GBApplicationSettingsProvider *settings2 = [self settingsByRunningWithArgs:@"--no-require-leader-for-local-crossrefs", nil]; + // verify + assertThatBool(settings1.requireLeaderForLocalCrossRefs, equalToBool(YES)); + assertThatBool(settings2.requireLeaderForLocalCrossRefs, equalToBool(NO)); +} + + #pragma mark Warnings settings testing - (void)testWarnOnMissingOutputPath_shouldAssignValueToSettings {