This document provides code style guidelines that AI agents MUST follow when working with this Objective-C codebase. These guidelines are adapted from industry best practices and tailored to match the existing code patterns in this repository.
- MUST: Absolute requirement
- MUST NOT: Absolute prohibition
- SHOULD: Recommended but may have valid reasons to ignore
- SHOULD NOT: Not recommended but may have valid reasons to use
- MAY: Optional
RECOMMENDED: Use dot notation for getting and setting properties.
// Preferred
view.backgroundColor = UIColor.orangeColor;
NSString *username = account.username;
// Avoid
[view setBackgroundColor:[UIColor orangeColor]];
NSString *username = [account username];MUST follow these spacing rules:
- Indentation: 4 spaces (never tabs)
- Opening braces on NEW line (repository convention)
- Closing braces on new line
- One blank line between methods
// Correct (as used in this repository)
- (instancetype)initWithUsername:(NSString *)username
homeAccountId:(MSALAccountId *)homeAccountId
environment:(NSString *)environment
{
self = [super init];
if (self)
{
_username = username;
_environment = environment;
_homeAccountId = homeAccountId;
}
return self;
}
// For if/else statements
if (user.isHappy)
{
// Do something
}
else
{
// Do something else
}MUST always use braces for conditional bodies, even for single-line statements.
// Correct
if (!error)
{
return success;
}
// Incorrect - Never do this
if (!error)
return success;
if (!error) return success;SHOULD only evaluate a single condition per ternary expression.
// Acceptable
result = account.isValid ? account : nil;
// Avoid - too complex
result = account.isValid ? account.username = tenant.isValid ? tenant.id : nil : nil;MUST check the return value, MUST NOT check the error variable directly.
// Correct
NSError *error;
if (![self trySomethingWithError:&error])
{
// Handle Error
}
// Incorrect - Apple APIs may write garbage to error on success
NSError *error;
[self trySomethingWithError:&error];
if (error)
{
// Handle Error
}SHOULD include space after scope symbol and between method segments.
// Correct
- (void)acquireTokenWithParameters:(MSALSilentTokenParameters *)parameters
completionBlock:(MSALCompletionBlock)completionBlock;
// For methods exceeding 80 characters, format like a form
- (MSALResult *)resultWithTokenResult:(MSIDTokenResult *)result
authScheme:(id<MSALAuthenticationSchemeProtocol>)authScheme
popManager:(MSIDDevicePopManager *)popManager
error:(NSError **)error;SHOULD use descriptive variable names:
NSString *username- clear and conciseNSString *accessToken- describes the token typeMSALAccount *currentAccount- not justaccountMSIDRequestParameters *requestParams- abbreviated but clearMSALPublicClientApplicationConfig *config- clear context
NOT RECOMMENDED: Single letter variable names (except loop counters)
MUST attach asterisks to variable name:
// Correct
NSString *clientId
// Incorrect
NSString* clientId
NSString * clientIdException: Constants (NSString * const MSALErrorDomain)
SHOULD use properties instead of naked instance variables.
// Preferred
@interface MSALAccount : NSObject
@property (nonatomic) NSString *username;
@property (nonatomic) NSString *environment;
@end
// Avoid
@interface MSALAccount : NSObject
{
NSString *username;
NSString *environment;
}
@endSHOULD avoid direct instance variable access except in:
- Initializer methods (
init,initWithCoder:) deallocmethods- Custom setters and getters
SHOULD place ARC qualifiers between asterisks and variable name:
NSString * __weak weakReference;
MSALAccount * __autoreleasing autoreleasedAccount;MUST use MSAL prefix for public classes and constants
MAY use MSID prefix for internal/shared classes
// Correct
static const NSTimeInterval MSALDefaultTokenRefreshInterval = 300.0;
static NSString * const MSALErrorDomain = @"MSALErrorDomain";
// Incorrect
static const NSTimeInterval refreshInterval = 300.0;MUST be camelCase with lowercase leading word.
NSString *accessToken;
MSALAccount *currentAccount;
MSIDRequestParameters *requestParams;MUST be camelCase with lowercase leading word and underscore prefix:
@implementation MSALPublicClientApplication
{
BOOL _validateAuthority;
WKWebView *_customWebview;
NSString *_defaultKeychainGroup;
}MUST prefix category methods with msal or msid to avoid collisions:
// Correct
@interface NSArray (MSALAccessors)
- (id)msalObjectOrNilAtIndex:(NSUInteger)index;
@end
// Incorrect - may conflict with other libraries
@interface NSArray (MSALAccessors)
- (id)objectOrNilAtIndex:(NSUInteger)index;
@endSHOULD explain why, not what. MUST keep comments up-to-date or delete them. NOT RECOMMENDED: Block comments (code should be self-documenting).
SHOULD use literals for NSString, NSDictionary, NSArray, NSNumber:
// Preferred
NSArray *scopes = @[@"user.read", @"mail.read", @"profile"];
NSDictionary *claims = @{@"id_token": @{@"auth_time": @{@"essential": @YES}}};
NSNumber *isEnabled = @YES;
NSNumber *timeout = @30;
// Avoid
NSArray *scopes = [NSArray arrayWithObjects:@"user.read", @"mail.read", @"profile", nil];Warning: Never pass nil into array/dictionary literals - causes crash.
MUST declare as static constants:
static NSString * const MSALErrorDomain = @"MSALErrorDomain";
static const CGFloat MSALDefaultTimeout = 30.0;
static const NSTimeInterval MSALTokenExpirationBuffer = 300.0;MAY use #define only when explicitly used as a macro.
MUST use NS_ENUM() for enumerations:
typedef NS_ENUM(NSInteger, MSALPromptType)
{
MSALPromptTypeDefault,
MSALPromptTypeLogin,
MSALPromptTypeConsent,
MSALPromptTypeSelectAccount
};SHALL declare private properties in class extensions in implementation file:
// In MSALPublicClientApplication.m
@interface MSALPublicClientApplication()
{
BOOL _validateAuthority;
WKWebView *_customWebview;
}
@property (nonatomic) MSALPublicClientApplicationConfig *internalConfig;
@property (nonatomic) MSIDExternalAADCacheSeeder *externalCacheSeeder;
@property (nonatomic) MSIDCacheConfig *msidCacheConfig;
@endSHOULD use thread-safe pattern with dispatch_once:
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[[self class] alloc] init];
});
return sharedInstance;
}MUST NOT group imports (repository convention).
// Correct (as used in this repository)
#import "MSALPublicClientApplication+Internal.h"
#import "MSALPromptType_Internal.h"
#import "MSALError.h"
#import "MSALTelemetryApiId.h"
#import "MSIDMacTokenCache.h"
#import "MSIDLegacyTokenCacheAccessor.h"
#import "MSIDDefaultTokenCacheAccessor.h"
// Do NOT group like this
// Frameworks
@import Foundation;
// MSAL Core
#import "MSALPublicClientApplication.h"SHOULD make first parameter the object sending the message:
// Correct
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
// Incorrect
- (void)didSelectTableRowAtIndexPath:(NSIndexPath *)indexPath;SHOULD use clear formatting for complex blocks:
__auto_type block = ^(MSALResult *result, NSError *msidError, id<MSIDRequestContext> context)
{
NSError *msalError = [MSALErrorConverter msalErrorFromMsidError:msidError
classifyErrors:YES
msalOauth2Provider:self.msalOauth2Provider];
if (!completionBlock) return;
if (parameters.completionBlockQueue)
{
dispatch_async(parameters.completionBlockQueue, ^{
completionBlock(result, msalError);
});
}
else
{
completionBlock(result, msalError);
}
};SHOULD keep physical files in sync with Xcode project structure. SHOULD reflect Xcode groups as filesystem folders. SHOULD group code by feature, not just by type. SHOULD enable "Treat Warnings as Errors" build setting.
- Match Existing Patterns: Analyze similar existing code before implementing
- Follow MSAL Conventions: Use
MSALprefix for public classes,MSIDfor internal code in CommonCore sub repository - Maintain Consistency: Match indentation, spacing, and naming in surrounding code
- Property-First: Use
@propertydeclarations rather than instance variables - Error Handling: Always check return values, never the error variable
- Thread Safety: Use
dispatch_oncefor singletons, consider thread safety for shared resources - Memory Management: Follow ARC patterns, be mindful of retain cycles
- Nil Safety: Never pass
nilto array/dictionary literals - Documentation: Add header documentation for public APIs
- Test Coverage: Consider how changes affect existing tests
- Preserve Style: Don't mix styles within a file
- Minimal Changes: Change only what's necessary
- Update Comments: Keep comments synchronized with code changes
- Deprecation: Use proper deprecation warnings when replacing APIs
- Backward Compatibility: Consider impact on existing integrations
NSError *msidError = nil;
BOOL result = [self performOperationWithError:&msidError];
if (!result)
{
if (error) *error = [MSALErrorConverter msalErrorFromMsidError:msidError];
return NO;
}__auto_type block = ^(MSALResult *result, NSError *error)
{
// Process result
if (!completionBlock) return;
if (parameters.completionBlockQueue)
{
dispatch_async(parameters.completionBlockQueue, ^{
completionBlock(result, error);
});
}
else
{
completionBlock(result, error);
}
};MSID_LOG_WITH_CTX_PII(MSIDLogLevelInfo, context,
@"Operation completed with account %@",
MSID_PII_LOG_EMAIL(account.username));- Uses 4-space indentation (no tabs)
- Opening braces on new line
- All conditionals have braces
- Error handling checks return value, not error variable
- Method signatures properly spaced
- Variables descriptively named
- Pointers attached to variable names
- Uses properties instead of instance variables
- Category methods prefixed with
msalormsid - Uses
NS_ENUMfor enumerations - Private properties in class extension
- Singletons use
dispatch_once - Imports not grouped (per repository style)
- Delegate methods include sender as first parameter
- No warnings or errors in build
- Follows existing MSAL/MSID patterns
- Apple: The Objective-C Programming Language
- Apple: Coding Guidelines for Cocoa
- Apple: Memory Management Programming Guide
- IETF RFC 2119: Key words for use in RFCs
- Braces on New Line: Unlike many Objective-C style guides, this repository places opening braces on a new line
- No Import Grouping: Imports are listed without grouping or comments
- MSAL/MSID Prefixes: Public APIs use
MSAL, internal/shared from CommonCore repository useMSID - Extensive Logging: PII-aware logging with
MSID_LOG_WITH_CTXmacros - Block-based Async: Completion handlers with queue dispatch patterns
All new files MUST include the Microsoft copyright header when added to this repository, but not when generating a new sample app:
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//------------------------------------------------------------------------------This style guide is adapted specifically for AI agents working on the Microsoft Authentication Library (MSAL) for iOS and macOS. When in doubt, prioritize consistency with existing codebase patterns over strict adherence to external style guides.
The Swift code under MSAL/src/native_auth) MUST follow the SwiftLint rules from MSAL/.swiftlint.yml.
line_length: warning at 150 columns.type_name: max length 60.function_parameter_count: warning at 7.- Disabled rules:
todo,empty_enum_arguments. - Default limits apply for
function_body_length(50),cyclomatic_complexity(10),file_length, andtype_body_length.
Changed native_auth Swift files MUST lint clean (zero warnings) before completion:
swiftlint lint --quiet MSAL/src/native_auth/<changed-file>.swiftWhen a call or declaration exceeds 150 columns, wrap it — put each argument on its own line, indented 4 spaces beyond the call, with the closing paren on its own line.
return await mapInteraction(
startResult,
flowType: .signIn,
username: parameters.username,
scopes: scopes,
event: event,
context: context
)Wrap long ternaries, makeState(...), response(.actionRequired(...)), and try self.requestProvider.foo(...) calls the same way. For a long nested constructor, break the inner initializer onto its own lines too:
return failure(
.error(MSALNativeAuthFlowError(
kind: .generalError,
errorDescription: "No usable sign-in method returned"
)),
event: event,
context: context
)Only suppress line_length inline — // swiftlint:disable:this line_length — for an un-wrappable single string literal (log/error message). Never use it to avoid wrapping ordinary code.
- One parameter per line when a declaration exceeds the line limit; closing paren and
-> ReturnTypeon their own line. - 4-space indentation, never tabs.
Long orchestration methods that legitimately exceed the 50-line body limit should suppress the warning rather than fragmenting the logic across helpers. Do not refactor control flow purely to satisfy the linter.
-
Add the suppression on the line immediately above the
func:// swiftlint:disable:next function_body_length private func handleResponse(...) { ... }
-
When a function trips both rules, combine them on one line (see
MSALNativeAuthTokenResponseValidator.swift):// swiftlint:disable:next cyclomatic_complexity function_body_length func validate(...) { ... }
-
For file- or type-level limits, use the block form at the top of the file / above the type:
// swiftlint:disable file_length // swiftlint:disable:next type_body_length final class MSALNativeAuth...Controller { ... }