Skip to content

Commit 7300d47

Browse files
committed
(PUP-5937) Detect duplicate keys in hashes
Before this commit, duplicate keys in hashes would go undetected This commit changes this so that a duplicate key results in a warning: "The key 'the_key' is declared more than once"
1 parent ddda4a1 commit 7300d47

File tree

6 files changed

+33
-7
lines changed

6 files changed

+33
-7
lines changed

lib/puppet/pops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ module Config
7777
end
7878

7979
module Evaluator
80+
require 'puppet/pops/evaluator/literal_evaluator'
8081
require 'puppet/pops/evaluator/callable_signature'
8182
require 'puppet/pops/evaluator/runtime3_converter'
8283
require 'puppet/pops/evaluator/runtime3_support'

lib/puppet/pops/evaluator/literal_evaluator.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
require 'rgen/ecore/ecore'
2-
1+
module Puppet::Pops
2+
module Evaluator
33
# Literal values for
44
# String (not containing interpolation)
55
# Numbers
@@ -14,14 +14,13 @@
1414
# Not considered literal
1515
# QualifiedReference # i.e. File, FooBar
1616
#
17-
class Puppet::Pops::Evaluator::LiteralEvaluator
18-
#include Puppet::Pops::Utils
17+
class LiteralEvaluator
1918

2019
EMPTY_STRING = ''.freeze
2120
COMMA_SEPARATOR = ', '.freeze
2221

2322
def initialize
24-
@@literal_visitor ||= Puppet::Pops::Visitor.new(self, "literal", 0, 0)
23+
@@literal_visitor ||= Visitor.new(self, "literal", 0, 0)
2524
end
2625

2726
def literal(ast)
@@ -70,7 +69,7 @@ def literal_LiteralRegularExpression(o)
7069

7170
def literal_ConcatenatedString(o)
7271
# use double quoted string value if there is no interpolation
73-
throw :not_literal unless o.segments.size == 1 && o.segments[0].is_a?(Puppet::Pops::Model::LiteralString)
72+
throw :not_literal unless o.segments.size == 1 && o.segments[0].is_a?(Model::LiteralString)
7473
o.segments[0].value
7574
end
7675

@@ -85,3 +84,5 @@ def literal_LiteralHash(o)
8584
end
8685
end
8786
end
87+
end
88+
end

lib/puppet/pops/issues.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,10 @@ def self.hard_issue(issue_code, *args, &block)
594594
"The parameter '#{param_name}' is declared more than once in the parameter list"
595595
end
596596

597+
DUPLICATE_KEY = issue :DUPLICATE_KEY, :key do
598+
"The key '#{key}' is declared more than once"
599+
end
600+
597601
RESERVED_PARAMETER = hard_issue :RESERVED_PARAMETER, :container, :param_name do
598602
"The parameter $#{param_name} redefines a built in parameter in #{label.the(container)}"
599603
end

lib/puppet/pops/validation/checker4_0.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ module Validation
1111
# Model objects via their metamodel. (I.e an extra call to multiplicity check in polymorph check).
1212
# This is however mostly valuable when validating model to model transformations, and is therefore T.B.D
1313
#
14-
class Checker4_0
14+
class Checker4_0 < Evaluator::LiteralEvaluator
1515
attr_reader :acceptor
1616
attr_reader :migration_checker
1717

1818
# Initializes the validator with a diagnostics producer. This object must respond to
1919
# `:will_accept?` and `:accept`.
2020
#
2121
def initialize(diagnostics_producer)
22+
super()
2223
@@check_visitor ||= Visitor.new(nil, "check", 0, 0)
2324
@@rvalue_visitor ||= Visitor.new(nil, "rvalue", 0, 0)
2425
@@hostname_visitor ||= Visitor.new(nil, "hostname", 1, 2)
@@ -467,6 +468,17 @@ def check_LiteralList(o)
467468
o.values.each {|v| rvalue(v) }
468469
end
469470

471+
def check_LiteralHash(o)
472+
# the keys of a literal hash may be non-literal expressions. They cannot be checked.
473+
unique = Set.new
474+
o.entries.each do |entry|
475+
catch(:not_literal) do
476+
literal_key = literal(entry.key)
477+
acceptor.accept(Issues::DUPLICATE_KEY, entry, {:key => literal_key}) if unique.add?(literal_key).nil?
478+
end
479+
end
480+
end
481+
470482
def check_NodeDefinition(o)
471483
# Check that hostnames are valid hostnames (or regular expressions)
472484
hostname(o.host_matches, o)

lib/puppet/pops/validation/validator_factory_4_0.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def severity_producer
2727

2828
p[Issues::FUTURE_RESERVED_WORD] = :deprecation
2929

30+
p[Issues::DUPLICATE_KEY] = :warning
3031
p[Issues::NAME_WITH_HYPHEN] = :error
3132
p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore
3233
p

spec/unit/pops/validator/validator_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ def validate(model)
3434
expect(validate(fqn('::_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
3535
end
3636

37+
it 'produces a warning for duplicate keyes in a literal hash' do
38+
acceptor = validate(parse('{ a => 1, a => 2 }'))
39+
expect(acceptor.warning_count).to eql(1)
40+
expect(acceptor.error_count).to eql(0)
41+
expect(acceptor).to have_issue(Puppet::Pops::Issues::DUPLICATE_KEY)
42+
end
43+
3744
context 'for non productive expressions' do
3845
[ '1',
3946
'3.14',

0 commit comments

Comments
 (0)