Respect is resource inspector and lint tool to help find issues with resources in Xcode projects. It is currently focused on iOS projects but it could with some effort also be used with other kinds of Xcode projects.
It can be used to find issues like:
- Missing files, image and device/scale images
- Unused resources
- Incorrectly named image files
- Incorrect filename casing
- Resources that should not be included in the bundle
- Bundle path collisions
brew install --HEAD https://raw.github.com/wader/respect/master/misc/respect.rb
If you want to upgrade you should uninstall respect first.
git clone git://github.com/wader/respect.git
cd respect
xcodebuild
cp build/Release/respect /to/somewhere
Easiest is to use it as a Xcode build phase run scrip as it then can figure out project file path, target and build configuration by itself and also output warnings in a format readable by Xcode to show source annotations directly in the IDE. You can also use it as separate target or run it from a terminal.
Add a "Run script" to the target build phases. Example script:
PATH="$PATH:/usr/local/bin"
hash respect 2>/dev/null && respect || true
This script makes sure respect is installed before running it and also makes sure to not cause errors if not, which is probably a good idea if other people will work on the project that don't have respect installed.
Add an "External build system" target (under Add Target / OS X / Other):
Set build tool to the path of respect, e.g. /usr/local/bin/respect
and arguments to "$(PROJECT_FILE_PATH)" <TargetName>
where <TargetName>
should be replaced by the target name.
When running from a terminal respect will output warnings and statistics in a human readable format.
$ respect --help
Usage: respect XcodeProjectPath [-cnd] [TargetName] [ConfigurationName]
XcodeProjectPath Path to XcodeProject file or directory
TargetName (First native target) Native target name to lint
ConfigurationName (Release) Build configuration name
No arguments are required when running as a Xcode run script.
-c, --config Path Configuration file ($SRCROOT/.respect)
-n, --nodefault Don't use default configuration
-d, --dumpconfig Dump interpreted configuration
--spfeatures Path Spotify features path
Respect reads the Xcode project file and for the selected target and
configuration figures out which resources that will be copied to the
bundle. Then it uses the respect configuration found in the default
configuration, .respect
and source/header files to find resource
references in the source code. Finally it uses the collected resources
and references to report issues.
The configuration consist of matchers, actions and ignores. A matcher looks for resource names in some way and then performs associated actions when something is "matched". A action is used to actually reference resources and can be used with or without a associated matcher. If a action is associated with a matcher it can use the matched names when referencing resources, if it's not associated with any matcher the action will be performed just once.
To associate one or more actions with a matcher you simply list them on the
following lines after the matcher with no empty lines in between. If the
matcher "capture" any strings they can be referred to using $1
, $2
, ….
There is also $0
to refer to the whole matched string.
@LintSourceMatch: [UIImage imageNamed:@]
@LintImage: $1
This is the configuration to look for resource used by UIKit's
+[UIImage imageNamed:]
. Here @LintSourceMatch
is the matcher and
@LintImage
is the associated action using the captured argument
string $1
as image name.
This might mostly be a matter of taste but the different variants have pros and cons.
Putting all configuration into .respect
(or some other file using -c
)
makes it clear where all config is and do not "pollute" the source files.
Using a mix of configuration in both .respect
and source files have
the advantage that when you in the future remove source files static
actions and ignore configuration in those files are also removed.
In the case of source and resource matchers it does not matter that much where you put them as you will get warnings if they no longer match anything.
When embedding configuration inside source files you will probably need to put it inside comments:
// @LintSourceMatch: loadImage(@)
// @LintImage: $1
UIImage *loadImage(NSString *name) {
// load image name
}
But in .respect
there is no need to comment out configuration.
The first time you run respect you will most probably get some false warnings, most likely about unused resources. Go thru the warnings and try to find the location in the source code where the problem is.
If for a unused warning you can find a location(s) where the resource is actually used you could do (in preference order):
-
Try to rewrite to code to let the default configuration find the resource. E.g. don't use formatted strings to load images.
-
Add a matcher and actions to let respect find that kinds of resource references.
-
Add action without matcher.
-
Add ignore configuration to silence the warning.
As you add matchers and actions the number of unused resources will go down and respect will use the new configuration to find missing resources.
The default configuration included in respect have configuration for most of the iOS API that deals with resources so you will probably only need to write matchers for own resource methods.
@LintSourceMatch: expression signature | /regex/[ixsmw]
Matches source code using a "expression signature" or a regular expression. Both expression signature and regular expression can capture strings from the matched source code that then can be used in actions.
Expression signatures look similar to normal C call f(…)
and Objective-C
send [receiver selector]
syntax and can capture both C and Objective-C
string literals using @
and identifier names using $
. To ignore an argument
just leave it out f(, @, )
. It is also possible to have nested expression
signatures.
Examples:
@LintSourceMatch: [ImageLoader imageNamed:@ withConfig:@ ignored:]
Matches [ImageLoader imageNamed:@"image" withConfig:@"config" ignored:@"ignored"]
with $1
=image
, $2
=config
and ignored:
is ignored.
@LintSourceMatch: loadImage(@, @, )
Matches loadImage(@"image", @"config", @"ignored")
with $1
=image
,
$2
=config
and third argument is ignored.
@LintSourceMatch: [Class arg:function(@,) arg:$ arg:]
Matches [Class arg:function(@"string", 123) arg:var arg:"ignored"]
with
$1
=string
and $2
=var
. 123
and "ignored"
are ignored as the argument
signature is empty.
A special case is that receiver name can include *
for wildcard match:
@LintSourceMatch: [[*ViewController alloc] initWithNibName:@ bundle:]
Matches [[SomeViewController alloc] initWithNibName:@"SomeViewController" bundle:]
with $1
=SomeViewController
.
Regular expression are Cocoa flavoured regular expression. Look at the NSRegularExpression documentation for syntax and options.
@LintResourceMatch: fnmatch | /regex/[ixsmw]
Match fnmatch pattern (aka glob) or regular expression against all bundle resource paths.
Examples:
@LintResourceMatch: *.psd
@LintWarning: PSD file included in bundle
Available resource actions are @LintFile
, @LintImage
and @LintNib
.
They are used to reference resources in the app bundle. They check if
resources exist and marked them as used or warn if they are missing. All
of them also support a name permutation syntax and options to specify
required condition for the permutations.
The permutation syntax is a{b,c}
to get the permutations ab
and ac
.
Condition options are all
, any
and optional
. Where all
means that all
permutations needs to be fulfilled, any
at least one and optional
that
none need to be fulfilled. A action permutation is fulfilled if all required
resources exist. The default condition if not specified is all
.
Example:
@LintImage: image{,-selected} all
@LintImage: image-highlighted optional
@LintFile: image-{border,shadow}.plist any
This will require that image image
and image-selected
exist, image
image-highlighted
is optional and at least one of the files
image-border.plist
or image-shadow.plist
exist.
Note that in the case of @LintImage
"exist" could mean that required scales
must exist etc.
@LintFile: path
Simply reference resources by filename path.
@LintImage: name [options]
Is used to reference resources the way +[UIImage imageNamed:]
do. It support
scale, device and file extension options to configure how to lookup images.
Scale options are: @1x
(no scale in filename), @2x
and 568h
.
Device options are: ~ipad
, ~iphone
and ~any
(no device modifier in
filename).
File extension options are: png
jpg
jpeg
tiff
tif
gif
bmp
bmpf
ico
cur
xbm
Image options can be specified using @LintImageDefault
, with the action or
from the image name. The order in which options are set is default, action and
name e.g: if @1x
@2x
png
is the default image options, ~iphone
is
specified with the action and the image name is image~ipad.jpg
the resulting
options will be @1x
@2x
jpg
~ipad
.
If you don't specify any options for either scale, device or extension they will fallback to a wildcard behaviour. In that case of scale and device they will try to find as many existing images as possible. Wildcard extension work a bit different by using the the extension of the first image found (searched in the order specified above).
There is one special case if the image name ends with the suffix -568h
then
the suffix is kept and scale will be set to @2x
. E.g.: image name image-568h
will looks for image-568h@2x
images.
The table below give some examples for how the options behave:
Options | Behaviour |
---|---|
none | Look for all combinations of scale, device and first found extension.a.png a@2x.png a@2x~ipad.png …At least one must be found. |
png |
As above but only look for files with PNG extension. |
@1x @2x |
Require scale 1 and 2 for all combinations of device and first found extension. If a.png is found, require a.png a@2x.png If a~ipad.png is found, require a~ipad.png a@2x~ipad.png |
@1x @2x ~ipad png |
As above but only look for files with ~ipad device modifier and PNG extension.a~ipad.png a@2x~ipad.png |
~any ~iphone |
Only look for filenames with no device modifier or ~ipad device modifier. |
568h |
Looks for a -568h@2x image. |
@LintImage: name
Reference a NIB file and images referenced by the NIB. Images will be looked
up using @LintImageDefault
and modifiers in the image name. The .nib
extension is optional and language and device specific nibs will be checked
too.
@LintWarning: message
Can be used to generate warnings when a matcher matches. The default config
uses this action together with resource matchers (@LintResourceMatch:
) to
warn about wrongly named files and files that probably shouldn't be included
in the bundle.
There is also a @LintInfoPlist
action to look for resources referenced by
the Info.plist
file but you will probably not need to use it directly as the
default config already include a @LintInfoPlist: Info.plist
config line.
@LintIgnoreMissing: fnmatch | /regex/[ixsmw]
@LintIgnoreUnused: fnmatch | /regex/[ixsmw]
@LintIgnoreWarning: fnmatch | /regex/[ixsmw]
@LintIgnoreError: fnmatch | /regex/[ixsmw]
As respect might not be capable enough to reference some resources you may need to tell it to ignore some warning and errors. All ignore configurations take the same argument, a fnmatch pattern (aka glob) or a regular expression.
@LintIgnoreMissing
and @LintIgnoreUnused
matches against the relative
path in the bundle.
@LintIgnoreWarning
and @LintIgnoreError
matches against
the relative path in the source root.
Examples:
// @LintIgnoreMissing: images/missing{,@2x}
[UIImage imageNamed:@"images/missing"];
Ignore the missing resource reference to missing
and missing@2x
. Note that
the file extension is not included as no candidate files were found. If the
path had been [UIImage imageNamed:@"images/missing.png"]
the missing
resource paths would have been missing.png
and missing@2x.png
.
@LintIgnoreUnused: Some.bundle/*
Ignore unused warnings about all files in the directory Some.bundle
.
@LintIgnoreError: Project/generated.m
Ignore errors for the file Project/generated.m
, e.g. if the file
might be missing.
Some source code will be hard to find references in as the source matcher do not use a proper language parser but instead relies on regular expressions (even when using expression signatures).
Example:
This code is not suitable for matching:
NSString *imageName = nil;
if (some condition) {
imageName = @"enabled";
} else {
imageName = @"disabled";
}
UIImage *image = [UIImage imageNamed:imageName];
But can be rewritten to be:
UIImage *image = nil;
if (some condition) {
image = [UIImage imageNamed:@"enabled"];
} else {
image = [UIImage imageNamed:@"disabled"];
}
Or if you want to keep the code as similar as possible you could do something like:
// @LintSourceMatch: IMAGE(@)
// @LintImage: $1
#define IMAGE(s) s
NSString *imageName = nil;
if (some condition) {
imageName = IMAGE(@"enabled");
} else {
imageName = IMAGE(@"disabled");
}
UIImage *image = [UIImage imageNamed:imageName];
This is probably because a action is faulty not associated with any matcher,
e.g. a misspelled matcher config like @LinSourceMatch
instead of
@LintSourceMathch
.
Just leave out @
or $
for the argument:
@LintSourceMatch: [receiver ignoredArgument: matchedArgument:@]
And
@LintSourceMatch: loadImage(, @)
Will ignore the first argument and match a second string argument as $1
.
The default config is that @LintImage
require that both @1x and @2x exist
if one of them exist. You can change this using:
@LintImageDefault: @1x
Now the @2x image is not required.
Yes you can use a resource matches like this:
@LintResourceMatch: *.nib
@LintNib: $0
But note that this will mark all nib files in bundle as used so you won't get warnings about unused nib files.
Both matcher and action configuration support quotes so you can do:
@LintFile: "Path with whitespace/$1"
Escaping is also supported if you need to have literal quotes.
See the CONTRIBUTING.md file.
Thanks to Travis CI and Coveralls there are automatic builds, unit tests and code coverage reports. Current status for master branch:
@LintImage
does not behave exactly as +[UIImage imageNamed:]
, e.g.
[UIImage imageNamed:@"image~ipad.png"]
will on iOS fallback to
image.png
if image~ipad.png
is missing. Currently this will be reported
as image~ipad.png
is missing and image.png
will not be marked as used
if it exist.
Preprocessor directives like #if
etc are not supported.
Matching of concatenated literal strings are not supported.
Handling of #import
and #include
is limited. It currently only supports
local includes relative to the source file or directories configured with the
header search path build configuration. Also header files are a bit tricky as
they don't have a "target membership" in the project file so they will only
be linted if they are included directly or indirectly by some target source
file.
Resources bundles are not handled and I'm not really sure how it could be done correctly.
When running from terminal some build configuration paths are guessed and some are missing.
You can specify multiple file extension options but in most cases it makes no sense.
With any
condition if one permutation is fulfilled one or more other
permutations might be "half" fulfilled and mark files as used.
Permutation syntax support nested {}
.
Capture strings are always unescaped.