The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

# Copyright 2011 Kevin Ryde

# This file is part of Perl-Critic-Pulp.
#
# Perl-Critic-Pulp is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 3, or (at your option) any
# later version.
#
# Perl-Critic-Pulp is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Perl-Critic-Pulp.  If not, see <http://www.gnu.org/licenses/>.


# @foo{1..10} hash slice keys
#

use 5.006;
use strict;
use Getopt::Long;
use Perl::Critic;
use Perl::Critic::Utils;
use Perl::Critic::Violation;

use PPI::Document;
use Perl6::Slurp;
use lib::abs '.';
use MyLocatePerl;

# uncomment this to run the ### lines
#use Smart::Comments;


my %zzz;
my $zzz = {};
my %dup = (
           x => 1,
           ,
           %zzz,
           %$zzz,
           x => 2);


my $verbose = 0;
my $l = MyLocatePerl->new (exclude_t => 1);
my $count;

{
  my $filename = 'devel/grep-hash-keys.pl';
  my $content = eval { Perl6::Slurp::slurp ($filename) } || next;
  file ($filename, $content);
}
{
  while (my ($filename, $content) = $l->next) {
    file ($filename, $content);
  }
  exit 0;
}

{
  my $filename = '/usr/share/perl5/CPAN/Meta/Validator.pm';
  my $content = eval { Perl6::Slurp::slurp ($filename) } || next;
  file ($filename, $content);
  exit 0;
}


sub file {
  my ($filename, $content) = @_;

  if ($verbose) {
    print "$filename\n";
  }

  my $doc = PPI::Document->new (\$content);
  if (! $doc) {
    print "$filename:1: cannot parse: $PPI::Document::errstr\n";
    return;
  }

  $count = 0;
  $doc->find_first (sub {
                      my ($doc, $elem) = @_;
                      examine($elem, $filename);
                      return 0;
                    });
  if ($verbose) {
    print "  looked at $count\n";
  }
}

sub examine {
  my ($elem, $filename) = @_;

  if ($elem->isa('PPI::Structure::Constructor')) {
    return if (substr($elem,0,1) eq '[');

    my $prev = $elem->sprevious_sibling;
    return if ($prev && $prev->isa('PPI::Token::Word') && $prev eq 'do');

  } elsif ($elem->isa('PPI::Structure::List')) {
    my $prev = $elem->sprevious_sibling || return;
    return unless $prev->isa('PPI::Token::Operator') && $prev eq '=';
    ### elem: "$elem"
    $prev = $prev->sprevious_sibling || return;
    while ($prev->isa('PPI::Structure::Subscript')) {
      $prev = $prev->sprevious_sibling || return;
    }
    ### prev: (ref $prev)."  $prev"
    ### symbol: $prev->symbol_type
    return unless ($prev->isa('PPI::Token::Symbol')
                   && $prev->symbol_type eq '%');

  } else {
    return;
  }

  ### examine: (ref $elem)."  $elem"
  $elem = $elem->schild(0) || return;
  if ($elem->isa('PPI::Statement::Expression')) {
    $elem = $elem->schild(0) || return;
  }
  $count++;


  my @nodes = _elem_and_ssiblings ($elem);
  my @arefs = Perl::Critic::Utils::split_nodes_on_comma(@nodes);
  ### @nodes
  ### @arefs

  # ignore empty commas
  @arefs = grep {defined} @arefs;

  my %seen;
  for (;;) {
    my $aref = shift @arefs || return;  # key
    my $value_aref = shift @arefs; # value

    ### key: $aref
    ### value: $value_aref

    $elem = $aref->[0];
    ### key: (ref $elem)."  $elem"

    # %$foo is an even number of things
    if ($elem->isa('PPI::Token::Cast')
        && $elem eq '%') {
      unshift @arefs, $value_aref;
      next;
    }

    my $str;
    if (@$aref == 1) {
      # %foo is an even number of things
      if ($elem->isa('PPI::Token::Symbol')
          && $elem->symbol_type eq '%') {
        unshift @arefs, $value_aref;
        next;
      }

      if ($elem->isa('PPI::Token::Word')) {
        $str = $elem;
      } elsif ($elem->isa('PPI::Token::Quote')) {
        $str = $elem->string;
      }
    }
    ### $str

    if (defined $str && $seen{$str}++) {
      my $linenum = $elem->line_number;
      print "$filename:$linenum: duplicate key $str\n";
    }
  }
}

sub _elem_and_ssiblings {
  my ($elem) = @_;
  my @ret;
  while ($elem) {
    push @ret, $elem;
    $elem = $elem->snext_sibling;
  }
  return @ret;
}

sub elem_is_comma_operator {
  my ($elem) = @_;
  return ($elem->isa('PPI::Token::Operator')
          && $Perl::Critic::Pulp::Utils::COMMA{$elem});
}
exit 0;










    # my $comma = $elem->snext_sibling;
    # if ($comma && ! elem_is_comma_operator($comma)) {
    #   my $linenum = $comma->line_number;
    #   print "$filename:$linenum: stop at unknown comma $comma\n";
    #   return;
    # }
    # 
    # $elem = $comma || return;
    # do {
    #   $elem = $elem->snext_sibling || return;
    #   ### value: (ref $elem)."  $elem"
    # } until (elem_is_comma_operator($elem));
    # $elem = $elem->snext_sibling || return;