Skip to content

Commit

Permalink
Add StrNode#single_quoted?, StrNode#double_quoted? and `StrNode#p…
Browse files Browse the repository at this point in the history
…ercent_literal?` to simplify checking for string delimiters
  • Loading branch information
dvandersluis authored and marcandre committed Dec 13, 2024
1 parent 0c0a10d commit 2abbc5f
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog/change_add_strnodepercent_literal_to_simplify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#343](https://github.com/rubocop/rubocop-ast/pull/343): Add `StrNode#single_quoted?`, `StrNode#double_quoted?` and `StrNode#percent_literal?` to simplify checking for string delimiters. ([@dvandersluis][])
36 changes: 36 additions & 0 deletions lib/rubocop/ast/node/str_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,49 @@ module AST
class StrNode < Node
include BasicLiteralNode

PERCENT_LITERAL_TYPES = {
:% => /\A%(?=[^a-zA-Z])/,
:q => /\A%q/,
:Q => /\A%Q/
}.freeze

def single_quoted?
loc_is?(:begin, "'")
end

def double_quoted?
loc_is?(:begin, '"')
end

def character_literal?
loc_is?(:begin, '?')
end

def heredoc?
loc.is_a?(Parser::Source::Map::Heredoc)
end

# Checks whether the string literal is delimited by percent brackets.
#
# @overload percent_literal?
# Check for any string percent literal.
#
# @overload percent_literal?(type)
# Check for a string percent literal of type `type`.
#
# @param type [Symbol] an optional percent literal type
#
# @return [Boolean] whether the string is enclosed in percent brackets
def percent_literal?(type = nil)
opening_delimiter = loc.begin if loc.respond_to?(:begin)
return false unless opening_delimiter

if type
opening_delimiter.source.match?(PERCENT_LITERAL_TYPES.fetch(type))
else
opening_delimiter.source.start_with?('%')
end
end
end
end
end
69 changes: 69 additions & 0 deletions spec/rubocop/ast/dstr_node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,73 @@
it { is_expected.to eq('foo bar baz') }
end
end

describe '#single_quoted?' do
context 'with a double-quoted string' do
let(:source) { '"#{foo}"' }

it { is_expected.not_to be_single_quoted }
end

context 'with a %() delimited string' do
let(:source) { '%(#{foo})' }

it { is_expected.not_to be_single_quoted }
end

context 'with a %Q() delimited string' do
let(:source) { '%Q(#{foo})' }

it { is_expected.not_to be_single_quoted }
end
end

describe '#double_quoted?' do
context 'with a double-quoted string' do
let(:source) { '"#{foo}"' }

it { is_expected.to be_double_quoted }
end

context 'with a %() delimited string' do
let(:source) { '%(#{foo})' }

it { is_expected.not_to be_double_quoted }
end

context 'with a %Q() delimited string' do
let(:source) { '%Q(#{foo})' }

it { is_expected.not_to be_double_quoted }
end
end

describe '#percent_literal?' do
context 'with a quoted string' do
let(:source) { '"#{foo}"' }

it { is_expected.not_to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'with a %() delimited string' do
let(:source) { '%(#{foo})' }

it { is_expected.to be_percent_literal }
it { is_expected.to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'with a %Q() delimited string' do
let(:source) { '%Q(#{foo})' }

it { is_expected.to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.to be_percent_literal(:Q) }
end
end
end
173 changes: 172 additions & 1 deletion spec/rubocop/ast/str_node_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::StrNode do
subject(:str_node) { parse_source(source).ast }
subject(:str_node) { parsed_source.ast }

let(:parsed_source) { parse_source(source) }

describe '.new' do
context 'with a normal string' do
Expand Down Expand Up @@ -30,6 +32,98 @@
end
end

describe '#single_quoted?' do
context 'with a single-quoted string' do
let(:source) { "'foo'" }

it { is_expected.to be_single_quoted }
end

context 'with a double-quoted string' do
let(:source) { '"foo"' }

it { is_expected.not_to be_single_quoted }
end

context 'with a %() delimited string' do
let(:source) { '%(foo)' }

it { is_expected.not_to be_single_quoted }
end

context 'with a %q() delimited string' do
let(:source) { '%q(foo)' }

it { is_expected.not_to be_single_quoted }
end

context 'with a %Q() delimited string' do
let(:source) { '%Q(foo)' }

it { is_expected.not_to be_single_quoted }
end

context 'with a character literal' do
let(:source) { '?x' }

it { is_expected.not_to be_single_quoted }
end

context 'with an undelimited string within another node' do
subject(:str_node) { parsed_source.ast.child_nodes.first }

let(:source) { '/string/' }

it { is_expected.not_to be_single_quoted }
end
end

describe '#double_quoted?' do
context 'with a single-quoted string' do
let(:source) { "'foo'" }

it { is_expected.not_to be_double_quoted }
end

context 'with a double-quoted string' do
let(:source) { '"foo"' }

it { is_expected.to be_double_quoted }
end

context 'with a %() delimited string' do
let(:source) { '%(foo)' }

it { is_expected.not_to be_double_quoted }
end

context 'with a %q() delimited string' do
let(:source) { '%q(foo)' }

it { is_expected.not_to be_double_quoted }
end

context 'with a %Q() delimited string' do
let(:source) { '%Q(foo)' }

it { is_expected.not_to be_double_quoted }
end

context 'with a character literal' do
let(:source) { '?x' }

it { is_expected.not_to be_double_quoted }
end

context 'with an undelimited string within another node' do
subject(:str_node) { parsed_source.ast.child_nodes.first }

let(:source) { '/string/' }

it { is_expected.not_to be_single_quoted }
end
end

describe '#character_literal?' do
context 'with a character literal' do
let(:source) { '?\n' }
Expand Down Expand Up @@ -83,4 +177,81 @@
it { is_expected.to be_heredoc }
end
end

describe '#percent_literal?' do
context 'with a single-quoted string' do
let(:source) { "'foo'" }

it { is_expected.not_to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'with a double-quoted string' do
let(:source) { '"foo"' }

it { is_expected.not_to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'with a %() delimited string' do
let(:source) { '%(foo)' }

it { is_expected.to be_percent_literal }
it { is_expected.to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'with a %q() delimited string' do
let(:source) { '%q(foo)' }

it { is_expected.to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'with a %Q() delimited string' do
let(:source) { '%Q(foo)' }

it { is_expected.to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.to be_percent_literal(:Q) }
end

context 'with a character literal?' do
let(:source) { '?x' }

it { is_expected.not_to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'with an undelimited string within another node' do
subject(:str_node) { parsed_source.ast.child_nodes.first }

let(:source) { '/string/' }

it { is_expected.not_to be_percent_literal }
it { is_expected.not_to be_percent_literal(:%) }
it { is_expected.not_to be_percent_literal(:q) }
it { is_expected.not_to be_percent_literal(:Q) }
end

context 'when given an invalid type' do
subject { str_node.percent_literal?(:x) }

let(:source) { '%q(foo)' }

it 'raises a KeyError' do
expect { str_node.percent_literal?(:x) }.to raise_error(KeyError, 'key not found: :x')
end
end
end
end

0 comments on commit 2abbc5f

Please sign in to comment.