Skip to content

Commit

Permalink
Add StrNode#single_quoted?, StrNode#double_quoted? and StrNode#pe…
Browse files Browse the repository at this point in the history
…rcent_literal?` to simplify checking for string delimiters
  • Loading branch information
dvandersluis committed Dec 12, 2024
1 parent 944ced4 commit 12581e3
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 2 deletions.
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][])
45 changes: 44 additions & 1 deletion lib/rubocop/ast/node/str_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,56 @@ module AST
class StrNode < Node
include BasicLiteralNode

PERCENT_LITERAL_TYPES = {
:% => /^%(?![qQ])/,
:q => /^%q/,
:Q => /^%Q/
}.freeze

def single_quoted?
opening_delimiter&.is?("'")
end

def double_quoted?
opening_delimiter&.is?('"')
end

def character_literal?
loc.respond_to?(:begin) && loc.begin&.is?('?')
opening_delimiter&.is?('?')
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)
return false unless opening_delimiter

if type
opening_delimiter.source =~ PERCENT_LITERAL_TYPES[type]
else
opening_delimiter.source.start_with?('%')
end
end

private

def opening_delimiter
return unless loc.respond_to?(:begin)

loc.begin
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
142 changes: 141 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,86 @@
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 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 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 +165,62 @@
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 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
end
end

0 comments on commit 12581e3

Please sign in to comment.