Skip to content

Commit

Permalink
Allow passing file contents via standard input
Browse files Browse the repository at this point in the history
Various third-party tools want the ability to lint a file passed via
standard input. A quirk of this feature is that there's no way to
specify a file name in this way, so we need to expose a flag that can be
specified in addition so that the appropriate linter configuration can
be applied based on the path.
  • Loading branch information
sds committed Nov 22, 2015
1 parent 0242389 commit 8ad337a
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
pseudo elements and single colons with pseudo classes
* Add `disabled_properties` option to `PropertySpelling` allowing properties
to be prohibited from use
* Add support for linting files via STDIN by specifying `--stdin-file-path`
so the appropriate configuration can be applied based on the path

## 0.42.2

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ You can also specify a list of files explicitly:
scss-lint app/assets/stylesheets/**/*.css.scss
```

...or you can lint a file passed via standard input (**note** the
`--stdin-file-path` flag is required when passing via standard input):

```bash
cat some-file | scss-lint --stdin-file-path=path/to/treat/stdin/as/having.scss
```

`scss-lint` will output any problems with your SCSS, including the offending
filename and line number (if available).

Expand All @@ -80,6 +87,7 @@ Command Line Flag | Description
`-r`/`--require` | Require file/library (mind `$LOAD_PATH`, uses `Kernel.require`)
`-i`/`--include-linter` | Specify which linters you specifically want to run
`-x`/`--exclude-linter` | Specify which linters you _don't_ want to run
`--stdin-file-path` | When linting a file passed via standard input, treat it as having the specified path to apply the appropriate configuration
`-h`/`--help` | Show command line flag documentation
`--show-formatters` | Show all available formatters
`--show-linters` | Show all available linters
Expand Down
12 changes: 10 additions & 2 deletions lib/scss_lint/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,16 @@ def act_on_options(options)

def scan_for_lints(options, config)
runner = Runner.new(config)
runner.run(FileFinder.new(config).find(options[:files]))
report_lints(options, runner.lints, runner.files)
files =
if options[:stdin_file_path]
[{ file: STDIN, path: options[:stdin_file_path] }]
else
FileFinder.new(config).find(options[:files]).map do |file_path|
{ path: file_path }
end
end
runner.run(files)
report_lints(options, runner.lints, files)

if runner.lints.any?(&:error?)
halt :error
Expand Down
17 changes: 10 additions & 7 deletions lib/scss_lint/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class Engine
# @option options [String] :file The file to load
# @option options [String] :code The code to parse
def initialize(options = {})
if options[:file]
build_from_file(options[:file])
if options[:path]
build_from_file(options)
elsif options[:code]
build_from_string(options[:code])
end
Expand All @@ -41,11 +41,14 @@ def initialize(options = {})

private

# @param path [String]
def build_from_file(path)
@filename = path
@engine = Sass::Engine.for_file(path, ENGINE_OPTIONS)
@contents = File.open(path, 'r').read
# @param options [Hash]
# @option file [IO] if provided, us this as the file object
# @option path [String] path of file, loading from this if `file` object not
# given
def build_from_file(options)
@filename = options[:path]
@contents = options[:file] ? file.read : File.read(@filename)
@engine = Sass::Engine.new(@contents, ENGINE_OPTIONS.merge(filename: @filename))
end

# @param scss [String]
Expand Down
5 changes: 5 additions & 0 deletions lib/scss_lint/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ def add_file_options(parser)
@options[:excluded_files] = files
end

parser.on('--stdin-file-path file-path', String,
'Specify the path to assume for the file passed via STDIN') do |stdin_file_path|
@options[:stdin_file_path] = stdin_file_path
end

parser.on('-o', '--out path', 'Write output to a file instead of STDOUT', String) do |path|
define_output_path(path)
end
Expand Down
18 changes: 10 additions & 8 deletions lib/scss_lint/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def initialize(config)
@linters.map!(&:new)
end

# @param files [Array]
# @param files [Array<Hash>] list of file object/path hashes
def run(files)
@files = files
@files.each do |file|
Expand All @@ -22,16 +22,18 @@ def run(files)

private

# @param file [String]
# @param file [Hash]
# @option file [String] File object
# @option path [String] path to File (determines which Linter config to apply)
def find_lints(file)
engine = Engine.new(file: file)
engine = Engine.new(file)

@linters.each do |linter|
begin
run_linter(linter, engine, file)
run_linter(linter, engine, file[:path])
rescue => error
raise SCSSLint::Exceptions::LinterError,
"#{linter.class} raised unexpected error linting file #{file}: " \
"#{linter.class} raised unexpected error linting file #{file[:path]}: " \
"'#{error.message}'",
error.backtrace
end
Expand All @@ -40,12 +42,12 @@ def find_lints(file)
@lints << Lint.new(nil, ex.sass_filename, Location.new(ex.sass_line),
"Syntax Error: #{ex}", :error)
rescue FileEncodingError => ex
@lints << Lint.new(nil, file, Location.new, ex.to_s, :error)
@lints << Lint.new(nil, file[:path], Location.new, ex.to_s, :error)
end

# For stubbing in tests.
def run_linter(linter, engine, file)
return if @config.excluded_file_for_linter?(file, linter)
def run_linter(linter, engine, file_path)
return if @config.excluded_file_for_linter?(file_path, linter)
@lints += linter.run(engine, @config.linter_options(linter))
end
end
Expand Down
14 changes: 14 additions & 0 deletions spec/scss_lint/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,20 @@ def safe_run
end
end

context 'when the --stdin-file-path argument is specified' do
let(:flags) { ['--stdin-file-path', 'some-fake-file-path.scss'] }

before do
STDIN.stub(:read).and_return('// Nothing interesting')
end

it 'passes STDIN and the file path as a file tuple to the runner' do
SCSSLint::Runner.any_instance.should_receive(:run)
.with([file: STDIN, path: 'some-fake-file-path.scss'])
safe_run
end
end

context 'when specified SCSS file globs match no files' do
before do
SCSSLint::FileFinder.any_instance.stub(:find)
Expand Down
4 changes: 2 additions & 2 deletions spec/scss_lint/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class SCSSLint::Linter::FakeLinter1 < SCSSLint::Linter; end
class SCSSLint::Linter::FakeLinter2 < SCSSLint::Linter; end

describe '#run' do
let(:files) { ['dummy1.scss', 'dummy2.scss'] }
let(:files) { [{ path: 'dummy1.scss' }, { path: 'dummy2.scss' }] }
subject { runner.run(files) }

before do
Expand Down Expand Up @@ -112,7 +112,7 @@ class SCSSLint::Linter::FakeLinter2 < SCSSLint::Linter; end
end

it 'has the name of the file the linter was checking' do
expect { subject }.to raise_error { |e| e.message.should include files.first }
expect { subject }.to raise_error { |e| e.message.should include files.first[:path] }
end

it 'has the same backtrace as the original error' do
Expand Down

0 comments on commit 8ad337a

Please sign in to comment.