Commit 28c50294 authored by Fabio Pelosin's avatar Fabio Pelosin

[Linter] Extracted the Linter and added `pod repo lint`.

Closes #423.
parent f500613a
......@@ -5,6 +5,7 @@ module Pod
autoload :ErrorReport, 'cocoapods/command/error_report'
autoload :Install, 'cocoapods/command/install'
autoload :List, 'cocoapods/command/list'
autoload :Linter, 'cocoapods/command/linter'
autoload :Presenter, 'cocoapods/command/presenter'
autoload :Push, 'cocoapods/command/push'
autoload :Repo, 'cocoapods/command/repo'
......
module Pod
class Command
class Linter
include Config::Mixin
# TODO: Add check to ensure that attributes inherited by subspecs are not duplicated ?
attr_accessor :quick, :lenient, :no_clean
attr_reader :spec, :file
attr_reader :errors, :warnings, :notes
def initialize(podspec)
@file = podspec
end
def spec_name
file.basename('.*').to_s
end
# Takes an array of podspec files and lints them all
#
# It returns true if the spec passed validation
#
def lint
@errors, @warnings, @notes = [], [], []
@platform_errors, @platform_warnings, @platform_notes = {}, {}, {}
if !deprecation_errors.empty?
@errors = deprecation_errors
else
@spec = Specification.from_file(file)
platforms = spec.available_platforms
platforms.each do |platform|
@platform_errors[platform], @platform_warnings[platform], @platform_notes[platform] = [], [], []
spec.activate_platform(platform)
@platform = platform
puts "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed if config.verbose? && !@quick
# Skip validation if there are errors in the podspec as it would result in a crash
if !podspec_errors.empty?
@platform_errors[platform] += podspec_errors
@platform_notes[platform] << "#{platform.name} [!] Fatal errors found skipping the rest of the validation"
else
@platform_warnings[platform] += podspec_warnings
peform_extensive_analysis unless quick
end
end
# Get common messages
@errors = @platform_errors.values.reduce(:&)
@warnings = @platform_warnings.values.reduce(:&)
@notes = @platform_notes.values.reduce(:&)
platforms.each do |platform|
# Mark platform specific messages
@errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
@warnings += (@platform_warnings[platform] - @warnings).map {|m| "[#{platform}] #{m}"}
@notes += (@platform_notes[platform] - @notes).map {|m| "[#{platform}] #{m}"}
end
end
valid?
end
def valid?
lenient ? errors.empty? : ( errors.empty? && warnings.empty? )
end
def result_type
return :error unless errors.empty?
return :warning unless warnings.empty?
return :note unless notes.empty?
:success
end
# Performs platform specific analysis.
# It requires to download the source at each iteration
#
def peform_extensive_analysis
set_up_lint_environment
install_pod
puts "Building with xcodebuild.\n".yellow if config.verbose?
# treat xcodebuild warnings as notes because the spec maintainer might not be the author of the library
xcodebuild_output.each { |msg| ( msg.include?('error: ') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
@platform_errors[@platform] += file_patterns_errors
@platform_warnings[@platform] += file_patterns_warnings
tear_down_lint_environment
end
def install_pod
podfile = podfile_from_spec
config.verbose
installer = Installer.new(podfile)
installer.install!
@pod = installer.pods.find { |pod| pod.top_specification == spec }
config.silent
end
def podfile_from_spec
name = spec.name
podspec = file.realpath.to_s
platform = @platform
podfile = Pod::Podfile.new do
platform(platform.to_sym, platform.deployment_target)
pod name, :podspec => podspec
end
podfile
end
def set_up_lint_environment
tmp_dir.rmtree if tmp_dir.exist?
tmp_dir.mkpath
@original_config = Config.instance.clone
config.project_root = tmp_dir
config.project_pods_root = tmp_dir + 'Pods'
config.silent = !config.verbose
config.integrate_targets = false
config.generate_docs = false
end
def tear_down_lint_environment
tmp_dir.rmtree unless no_clean
Config.instance = @original_config
end
def tmp_dir
Pathname.new('/tmp/CocoaPods/Lint')
end
def pod_dir
tmp_dir + 'Pods' + spec.name
end
# It reads a podspec file and checks for strings corresponding
# to features that are or will be deprecated
#
# @return [Array<String>]
#
def deprecation_errors
text = @file.read
deprecations = []
deprecations << "`config.ios?' and `config.osx?' are deprecated" if text. =~ /config\..?os.?/
deprecations << "clean_paths are deprecated and ignored (use preserve_paths)" if text. =~ /clean_paths/
deprecations
end
# @return [Array<String>] List of the fatal defects detected in a podspec
def podspec_errors
messages = []
messages << "The name of the spec should match the name of the file" unless names_match?
messages << "Unrecognized platfrom" unless platform_valid?
messages << "Missing name" unless spec.name
messages << "Missing version" unless spec.version
messages << "Missing summary" unless spec.summary
messages << "Missing homepage" unless spec.homepage
messages << "Missing author(s)" unless spec.authors
messages << "Missing or invalid source: #{spec.source}" unless source_valid?
# attributes with multiplatform values
return messages unless platform_valid?
messages << "The spec appears to be empty (no source files, resources, or preserve paths)" if spec.source_files.empty? && spec.subspecs.empty? && spec.resources.empty? && spec.preserve_paths.empty?
messages += paths_starting_with_a_slash_errors
messages += deprecation_errors
messages
end
def names_match?
return true unless spec.name
root_name = spec.name.match(/[^\/]*/)[0]
file.basename.to_s == root_name + '.podspec'
end
def platform_valid?
!spec.platform || [:ios, :osx].include?(spec.platform.name)
end
def source_valid?
spec.source && !(spec.source =~ /http:\/\/EXAMPLE/)
end
def paths_starting_with_a_slash_errors
messages = []
%w[source_files public_header_files resources clean_paths].each do |accessor|
patterns = spec.send(accessor.to_sym)
# Some values are multiplaform
patterns = patterns.is_a?(Hash) ? patterns.values.flatten(1) : patterns
patterns = patterns.compact # some patterns may be nil (public_header_files, for instance)
patterns.each do |pattern|
# Skip FileList that would otherwise be resolved from the working directory resulting
# in a potentially very expensi operation
next if pattern.is_a?(FileList)
invalid = pattern.is_a?(Array) ? pattern.any? { |path| path.start_with?('/') } : pattern.start_with?('/')
if invalid
messages << "Paths cannot start with a slash (#{accessor})"
break
end
end
end
messages
end
# @return [Array<String>] List of the **non** fatal defects detected in a podspec
def podspec_warnings
license = spec.license || {}
source = spec.source || {}
text = @file.read
messages = []
messages << "Missing license type" unless license[:type]
messages << "Sample license type" if license[:type] && license[:type] =~ /\(example\)/
messages << "Invalid license type" if license[:type] && license[:type] =~ /\n/
messages << "The summary is not meaningful" if spec.summary =~ /A short description of/
messages << "The description is not meaningful" if spec.description && spec.description =~ /An optional longer description of/
messages << "The summary should end with a dot" if spec.summary !~ /.*\./
messages << "The description should end with a dot" if spec.description !~ /.*\./ && spec.description != spec.summary
messages << "Git sources should specify either a tag or a commit" if source[:git] && !source[:commit] && !source[:tag]
messages << "Github repositories should end in `.git'" if github_source? && source[:git] !~ /.*\.git/
messages << "Github repositories should use `https' link" if github_source? && source[:git] !~ /https:\/\/github.com/
messages << "Comments must be deleted" if text.scan(/^\s*#/).length > 24
messages
end
def github_source?
spec.source && spec.source[:git] =~ /github.com/
end
# It creates a podfile in memory and builds a library containing
# the pod for all available platfroms with xcodebuild.
#
# @return [Array<String>]
#
def xcodebuild_output
return [] if `which xcodebuild`.strip.empty?
messages = []
output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
clean_output = process_xcode_build_output(output)
messages += clean_output
puts(output) if config.verbose?
messages
end
def process_xcode_build_output(output)
output_by_line = output.split("\n")
selected_lines = output_by_line.select do |l|
l.include?('error: ') && (l !~ /errors? generated\./) && (l !~ /error: \(null\)/)\
|| l.include?('warning: ') && (l !~ /warnings? generated\./)\
|| l.include?('note: ') && (l !~ /expanded from macro/)
end
selected_lines.map do |l|
new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
new.gsub!(/^ */,' ') # Remove indentation
"XCODEBUILD > " << new # Mark
end
end
# It checks that every file pattern specified in a spec yields
# at least one file. It requires the pods to be alredy present
# in the current working directory under Pods/spec.name.
#
# @return [Array<String>]
#
def file_patterns_errors
messages = []
messages << "The sources did not match any file" if !spec.source_files.empty? && @pod.source_files.empty?
messages << "The resources did not match any file" if !spec.resources.empty? && @pod.resource_files.empty?
messages << "The preserve_paths did not match any file" if !spec.preserve_paths.empty? && @pod.preserve_files.empty?
messages << "The exclude_header_search_paths did not match any file" if !spec.exclude_header_search_paths.empty? && @pod.headers_excluded_from_search_paths.empty?
messages
end
def file_patterns_warnings
messages = []
unless @pod.license_file || spec.license && ( spec.license[:type] == 'Public Domain' || spec.license[:text] )
messages << "Unable to find a license file"
end
messages
end
end
end
end
......@@ -11,10 +11,16 @@ module Pod
Clones `URL' in the local spec-repos directory at `~/.cocoapods'. The
remote can later be referred to by `NAME'.
$ pod repo update NAME
$ pod repo update [NAME]
Updates the local clone of the spec-repo `NAME'. If `NAME' is omitted
this will update all spec-repos in `~/.cocoapods'.}
this will update all spec-repos in `~/.cocoapods'.
$ pod repo update [NAME | DIRECTORY]
Lints the spec-repo `NAME'. If a directory is provided it is assumed
to be the root of a repo. Finally, if NAME is not provided this will
lint all the spec-repos known to CocoaPods.}
end
extend Executable
......@@ -27,7 +33,7 @@ module Pod
raise Informative, "#{@action == 'add' ? 'Adding' : 'Updating the remote of'} a repo needs a `name' and a `url'."
end
@branch = argv.arguments[3]
when 'update'
when 'update', 'lint'
@name = argv.arguments[1]
else
super
......@@ -66,6 +72,50 @@ module Pod
end
end
def lint
if @name
dirs = File.exists?(@name) ? [ Pathname.new(@name) ] : [ dir ]
else
dirs = config.repos_dir.children.select {|c| c.directory?}
end
dirs.each do |dir|
check_versions(dir)
puts "\nLinting spec repo `#{dir.realpath.basename}'\n".yellow
podspecs = dir.glob('**/*.podspec')
invalid_count = 0
podspecs.each do |podspec|
linter = Linter.new(podspec)
linter.lenient = true
linter.quick = true
linter.lint
unless linter.result_type == :success
invalid_count += 1
case linter.result_type
when :error
color = :red
when :warning
color = :yellow
end
puts " -> ".send(color) << linter.spec_name
print_messages('ERROR', linter.errors)
print_messages('WARN', linter.warnings)
print_messages('NOTE', linter.notes)
puts unless config.silent?
end
end
puts "Analyzed #{podspecs.count} podspecs files.\n\n" unless config.silent?
invalid_count
end
end
def print_messages(type, messages)
return if config.silent?
messages.each {|msg| puts " - #{type.ljust(5)} | #{msg}"}
end
def check_versions(dir)
versions = versions(dir)
unless is_compatilbe(versions)
......
......@@ -73,7 +73,7 @@ module Pod
def lint
puts
invalid_count = lint_podspecs
count = specs_to_lint.count
count = podspecs_to_lint.count
if invalid_count == 0
lint_passed_message = count == 1 ? "#{podspecs_to_lint.first.basename} passed validation." : "All the specs passed validation."
puts lint_passed_message.green << "\n\n" unless config.silent?
......@@ -87,26 +87,27 @@ module Pod
def lint_podspecs
invalid_count = 0
specs_to_lint.each do |spec|
# Show immediatly which pod is being processed.
print " -> #{spec}\r" unless config.silent? || @multiple_files
$stdout.flush
linter = Linter.new(spec)
podspecs_to_lint.each do |podspec|
linter = Linter.new(podspec)
linter.lenient = @only_errors
linter.quick = @quick || @multiple_files
linter.no_clean = @no_clean
# Show immediatly which pod is being processed.
print " -> #{linter.spec_name}\r" unless config.silent? || @multiple_files
$stdout.flush
invalid_count += 1 unless linter.lint
# This overwrites the previously printed text
puts " -> ".send(lint_result_color(linter)) << spec.to_s unless config.silent? || should_skip?(linter)
print_messages(spec, 'ERROR', linter.errors)
print_messages(spec, 'WARN', linter.warnings)
print_messages(spec, 'NOTE', linter.notes)
puts " -> ".send(lint_result_color(linter)) << linter.spec_name unless config.silent? || should_skip?(linter)
print_messages('ERROR', linter.errors)
print_messages('WARN', linter.warnings)
print_messages('NOTE', linter.notes)
puts unless config.silent? || should_skip?(linter)
end
puts "Analyzed #{specs_to_lint.count} specs in #{podspecs_to_lint.count} podspecs files.\n\n" if @multiple_files && !config.silent?
puts "Analyzed #{podspecs_to_lint.count} podspecs files.\n\n" if @multiple_files && !config.silent?
invalid_count
end
......@@ -124,7 +125,7 @@ module Pod
@multiple_files && linter.errors.empty? && linter.warnings.empty? && linter.notes.empty?
end
def print_messages(spec, type, messages)
def print_messages(type, messages)
return if config.silent?
messages.each {|msg| puts " - #{type.ljust(5)} | #{msg}"}
end
......@@ -158,278 +159,6 @@ module Pod
Pathname.new('/tmp/CocoaPods/Lint_podspec')
end
def specs_to_lint
@specs_to_lint ||= begin
podspecs_to_lint.map do |podspec|
root_spec = Specification.from_file(podspec)
# TODO find a way to lint subspecs
# root_spec.preferred_dependency ? root_spec.subspec_dependencies : root_spec
end.flatten
end
end
# Linter class
#
class Linter
include Config::Mixin
# TODO: Add check to ensure that attributes inherited by subspecs are not duplicated ?
attr_accessor :quick, :lenient, :no_clean
attr_reader :spec, :file
attr_reader :errors, :warnings, :notes
def initialize(spec)
@spec = spec
@file = spec.defined_in_file.realpath
end
# Takes an array of podspec files and lints them all
#
# It returns true if the spec passed validation
#
def lint
@platform_errors, @platform_warnings, @platform_notes = {}, {}, {}
platforms = @spec.available_platforms
platforms.each do |platform|
@platform_errors[platform], @platform_warnings[platform], @platform_notes[platform] = [], [], []
@spec.activate_platform(platform)
@platform = platform
puts "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed if config.verbose? && !@quick
# Skip validation if there are errors in the podspec as it would result in a crash
if !podspec_errors.empty?
@platform_errors[platform] += podspec_errors
@platform_notes[platform] << "#{platform.name} [!] Fatal errors found skipping the rest of the validation"
else
@platform_warnings[platform] += podspec_warnings + deprecation_warnings
peform_extensive_analysis unless quick
end
end
# Get common messages
@errors = @platform_errors.values.reduce(:&) || []
@warnings = @platform_warnings.values.reduce(:&) || []
@notes = @platform_notes.values.reduce(:&) || []
platforms.each do |platform|
# Mark platform specific messages
@errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
@warnings += (@platform_warnings[platform] - @warnings).map {|m| "[#{platform}] #{m}"}
@notes += (@platform_notes[platform] - @notes).map {|m| "[#{platform}] #{m}"}
end
valid?
end
def valid?
lenient ? errors.empty? : ( errors.empty? && warnings.empty? )
end
# Performs platform specific analysis.
# It requires to download the source at each iteration
#
def peform_extensive_analysis
set_up_lint_environment
install_pod
puts "Building with xcodebuild.\n".yellow if config.verbose?
# treat xcodebuild warnings as notes because the spec maintainer might not be the author of the library
xcodebuild_output.each { |msg| ( msg.include?('error: ') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
@platform_errors[@platform] += file_patterns_errors
@platform_warnings[@platform] += file_patterns_warnings
tear_down_lint_environment
end
def install_pod
podfile = podfile_from_spec
config.verbose
installer = Installer.new(podfile)
installer.install!
@pod = installer.pods.find { |pod| pod.top_specification == @spec }
config.silent
end
def podfile_from_spec
name = spec.name
podspec = file.realpath.to_s
platform = @platform
podfile = Pod::Podfile.new do
platform(platform.to_sym, platform.deployment_target)
pod name, :podspec => podspec
end
podfile
end
def set_up_lint_environment
tmp_dir.rmtree if tmp_dir.exist?
tmp_dir.mkpath
@original_config = Config.instance.clone
config.project_root = tmp_dir
config.project_pods_root = tmp_dir + 'Pods'
config.silent = !config.verbose
config.integrate_targets = false
config.generate_docs = false
end
def tear_down_lint_environment
tmp_dir.rmtree unless no_clean
Config.instance = @original_config
end
def tmp_dir
Pathname.new('/tmp/CocoaPods/Lint')
end
def pod_dir
tmp_dir + 'Pods' + spec.name
end
# @return [Array<String>] List of the fatal defects detected in a podspec
def podspec_errors
messages = []
messages << "The name of the spec should match the name of the file" unless names_match?
messages << "Unrecognized platfrom" unless platform_valid?
messages << "Missing name" unless spec.name
messages << "Missing version" unless spec.version
messages << "Missing summary" unless spec.summary
messages << "Missing homepage" unless spec.homepage
messages << "Missing author(s)" unless spec.authors
messages << "Missing or invalid source: #{spec.source}" unless source_valid?
# attributes with multiplatform values
return messages unless platform_valid?
messages << "The spec appears to be empty (no source files, resources, or preserve paths)" if spec.source_files.empty? && spec.subspecs.empty? && spec.resources.empty? && spec.preserve_paths.empty?
messages += paths_starting_with_a_slash_errors
messages
end
def names_match?
return true unless spec.name
root_name = spec.name.match(/[^\/]*/)[0]
file.basename.to_s == root_name + '.podspec'
end
def platform_valid?
!spec.platform || [:ios, :osx].include?(spec.platform.name)
end
def source_valid?
spec.source && !(spec.source =~ /http:\/\/EXAMPLE/)
end
def paths_starting_with_a_slash_errors
messages = []
%w[source_files public_header_files resources clean_paths].each do |accessor|
patterns = spec.send(accessor.to_sym)
# Some values are multiplaform
patterns = patterns.is_a?(Hash) ? patterns.values.flatten(1) : patterns
patterns = patterns.compact # some patterns may be nil (public_header_files, for instance)
patterns.each do |pattern|
# Skip FileList that would otherwise be resolved from the working directory resulting
# in a potentially very expensi operation
next if pattern.is_a?(FileList)
invalid = pattern.is_a?(Array) ? pattern.any? { |path| path.start_with?('/') } : pattern.start_with?('/')
if invalid
messages << "Paths cannot start with a slash (#{accessor})"
break
end
end
end
messages
end
# @return [Array<String>] List of the **non** fatal defects detected in a podspec
def podspec_warnings
license = @spec.license || {}
source = @spec.source || {}
text = @file.read
messages = []
messages << "Missing license type" unless license[:type]
messages << "Sample license type" if license[:type] && license[:type] =~ /\(example\)/
messages << "Invalid license type" if license[:type] && license[:type] =~ /\n/
messages << "The summary is not meaningful" if spec.summary =~ /A short description of/
messages << "The description is not meaningful" if spec.description && spec.description =~ /An optional longer description of/
messages << "The summary should end with a dot" if @spec.summary !~ /.*\./
messages << "The description should end with a dot" if @spec.description !~ /.*\./ && @spec.description != @spec.summary
messages << "Git sources should specify either a tag or a commit" if source[:git] && !source[:commit] && !source[:tag]
messages << "Github repositories should end in `.git'" if github_source? && source[:git] !~ /.*\.git/
messages << "Github repositories should use `https' link" if github_source? && source[:git] !~ /https:\/\/github.com/
messages << "Comments must be deleted" if text.scan(/^\s*#/).length > 24
messages
end
def github_source?
@spec.source && @spec.source[:git] =~ /github.com/
end
# It reads a podspec file and checks for strings corresponding
# to features that are or will be deprecated
#
# @return [Array<String>]
#
def deprecation_warnings
text = @file.read
deprecations = []
deprecations << "`config.ios?' and `config.osx?' are deprecated" if text. =~ /config\..?os.?/
deprecations << "clean_paths are deprecated and ignored (use preserve_paths)" if text. =~ /clean_paths/
deprecations
end
# It creates a podfile in memory and builds a library containing
# the pod for all available platfroms with xcodebuild.
#
# @return [Array<String>]
#
def xcodebuild_output
return [] if `which xcodebuild`.strip.empty?
messages = []
output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
clean_output = process_xcode_build_output(output)
messages += clean_output
puts(output) if config.verbose?
messages
end
def process_xcode_build_output(output)
output_by_line = output.split("\n")
selected_lines = output_by_line.select do |l|
l.include?('error: ') && (l !~ /errors? generated\./) && (l !~ /error: \(null\)/)\
|| l.include?('warning: ') && (l !~ /warnings? generated\./)\
|| l.include?('note: ') && (l !~ /expanded from macro/)
end
selected_lines.map do |l|
new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
new.gsub!(/^ */,' ') # Remove indentation
"XCODEBUILD > " << new # Mark
end
end
# It checks that every file pattern specified in a spec yields
# at least one file. It requires the pods to be alredy present
# in the current working directory under Pods/spec.name.
#
# @return [Array<String>]
#
def file_patterns_errors
messages = []
messages << "The sources did not match any file" if !@spec.source_files.empty? && @pod.source_files.empty?
messages << "The resources did not match any file" if !@spec.resources.empty? && @pod.resource_files.empty?
messages << "The preserve_paths did not match any file" if !@spec.preserve_paths.empty? && @pod.preserve_files.empty?
messages << "The exclude_header_search_paths did not match any file" if !@spec.exclude_header_search_paths.empty? && @pod.headers_excluded_from_search_paths.empty?
messages
end
def file_patterns_warnings
messages = []
unless @pod.license_file || @spec.license && ( @spec.license[:type] == 'Public Domain' || @spec.license[:text] )
messages << "Unable to find a license file"
end
messages
end
end
# Templates and github information retrival for spec create
def default_data_for_template(name)
......
......@@ -9,6 +9,7 @@ describe "Pod::Command::Repo" do
it "runs with correct parameters" do
lambda { run_command('repo', 'add', 'NAME', 'URL') }.should.not.raise
lambda { run_command('repo', 'update') }.should.not.raise
lambda { run_command('repo', 'lint', temporary_directory.to_s) }.should.not.raise
end
it "complains for wrong parameters" do
......@@ -49,6 +50,16 @@ describe "Pod::Command::Repo" do
(repo2.dir + 'README').read.should.include 'Added!'
(repo3.dir + 'README').read.should.include 'Added!'
end
before do
config.repos_dir = fixture('spec-repos')
end
it "lints a repo" do
cmd = command('repo', 'lint', 'master')
lambda { cmd.run }.should.not.raise Pod::Informative
cmd.output.should.include "Missing license type"
end
end
describe "Concerning a repo support" do
......
......@@ -97,17 +97,6 @@ describe "Pod::Command::Spec#lint" do
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
before do
config.repos_dir = fixture('spec-repos')
end
it "lints a repo" do
# The fixture has warnings so it raises
cmd = command('spec', 'lint', "#{config.repos_dir}/master")
lambda { cmd.run }.should.raise Pod::Informative
cmd.output.should.include "WARN"
end
it "complains if it can't find any spec to lint" do
Dir.chdir(temporary_directory) do
lambda { command('spec', 'lint').run }.should.raise Pod::Informative
......
require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Command::Spec::Linter" do
describe "Pod::Command::Linter" do
extend SpecHelper::TemporaryDirectory
def write_podspec(text, name = 'JSONKit.podspec')
file = temporary_directory + 'JSONKit.podspec'
File.open(file, 'w') {|f| f.write(text) }
spec = Pod::Specification.from_file(file)
[spec, file]
file
end
def stub_podspec(pattern = nil, replacement = nil)
......@@ -18,16 +17,16 @@ describe "Pod::Command::Spec::Linter" do
end
it "fails a specifications that does not contain the minimum required attributes" do
spec, file = write_podspec('Pod::Spec.new do |s| end')
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec('Pod::Spec.new do |s| end')
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = true, true
linter.lint.should == false
linter.errors.join(' | ') =~ /name.*version.*summary.*homepage.*authors.*(source.*part_of).*source_files/
end
it "fails specifications if the name does not match the name of the file" do
spec, file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKitAAA'"))
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKitAAA'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = true, true
linter.lint.should == false
linter.errors.count.should == 1
......@@ -35,8 +34,8 @@ describe "Pod::Command::Spec::Linter" do
end
it "fails a specification if a path starts with a slash" do
spec, file = write_podspec(stub_podspec(/s.source_files = 'JSONKit\.\*'/, "s.source_files = '/JSONKit.*'"))
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/s.source_files = 'JSONKit\.\*'/, "s.source_files = '/JSONKit.*'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = true, true
linter.lint.should == false
linter.errors.count.should == 1
......@@ -44,8 +43,8 @@ describe "Pod::Command::Spec::Linter" do
end
it "fails a specification if the platform is unrecognized" do
spec, file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKit'\ns.platform = :iososx\n"))
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKit'\ns.platform = :iososx\n"))
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = true, true
linter.lint.should == false
linter.errors.count.should == 1
......@@ -53,8 +52,8 @@ describe "Pod::Command::Spec::Linter" do
end
it "fails validation if the specification contains warnings" do
spec, file = write_podspec(stub_podspec(/.*license.*/, ""))
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/.*license.*/, ""))
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = false, true
linter.lint.should == false
linter.errors.should.be.empty
......@@ -62,8 +61,8 @@ describe "Pod::Command::Spec::Linter" do
end
it "validates in lenient mode if there are no errors but there are warnings" do
spec, file = write_podspec(stub_podspec(/.*license.*/, ""))
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/.*license.*/, ""))
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = true, true
linter.lint.should == true
linter.errors.should.be.empty
......@@ -71,8 +70,8 @@ describe "Pod::Command::Spec::Linter" do
end
it "respects quick mode" do
spec, file = write_podspec(stub_podspec)
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec)
linter = Pod::Command::Spec::Linter.new(file)
linter.expects(:peform_multiplatform_analysis).never
linter.expects(:install_pod).never
linter.expects(:xcodebuild_output_for_platfrom).never
......@@ -82,25 +81,25 @@ describe "Pod::Command::Spec::Linter" do
end
it "produces deprecation notices" do
spec, file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\n if config.ios?\nend"))
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\n if config.ios?\nend"))
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = false, true
linter.lint.should == false
linter.errors.should.be.empty
linter.warnings.join(' | ').should =~ /`config.ios\?' and `config.osx\?' are deprecated/
linter.warnings.should.be.empty
linter.errors.join(' | ').should =~ /`config.ios\?' and `config.osx\?' are deprecated/
end
it "uses xcodebuild to generate notes and warnings" do
spec, file = write_podspec(stub_podspec)
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec)
linter = Pod::Command::Spec::Linter.new(file)
linter.lenient, linter.quick = false, false
linter.lint.should == false
linter.notes.join(' | ').should.include "JSONKit/JSONKit.m:1640:27: warning: equality comparison with extraneous parentheses" unless `which xcodebuild`.strip.empty?
end
it "checks for file patterns" do
spec, file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\ns.resources = 'WRONG_FOLDER'"))
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\ns.resources = 'WRONG_FOLDER'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.stubs(:xcodebuild_output).returns([])
linter.lenient, linter.quick = false, false
linter.lint.should == false
......@@ -108,9 +107,8 @@ describe "Pod::Command::Spec::Linter" do
end
it "uses the deployment target of the specification" do
spec, file = write_podspec(stub_podspec)
spec.stubs(:available_platforms).returns([Pod::Platform.new(:ios, "5.0")])
linter = Pod::Command::Spec::Linter.new(spec)
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKit'; s.platform = :ios, '5.0'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
podfile = linter.podfile_from_spec
......@@ -118,3 +116,4 @@ describe "Pod::Command::Spec::Linter" do
deployment_target.to_s.should == "5.0"
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment