Padre::Document::Perl::Beginner - naive implementation of some beginner specific error checking


Padre documentation Contained in the Padre distribution.

Index


Code Index:

NAME

Top

Padre::Document::Perl::Beginner - naive implementation of some beginner specific error checking

SYNOPSIS

Top

  use Padre::Document::Perl::Beginner;
  my $beginner = Padre::Document::Perl::Beginner->new;
  if (not $beginner->check($data)) {
      warn $beginner->error;
  }

DESCRIPTION

Top

This is a naive implementation. It needs to be replaced by one using PPI.

In Perl 5 there are lots of pitfalls the unaware, especially the beginner can easily fall in. While some might expect the Perl compiler itself would catch those it does not (yet ?) do it. So we took the initiative and added a beginners mode to Padre in which these extra issues are checked. Some are real problems that would trigger an error anyway we just make them a special case with a more specific error message. (e.g. use warning; without the trailing s) Others are valid code that can be useful in the hands of a master but that are poisonous when written by mistake by someone who does not understand them. (e.g. if ($x = /value/) { } ).

This module provides a method called check that can check a Perl script (provided as parameter as a single string) and recognize problematic code.

Examples

Top

See http://padre.perlide.org/ticket/52 and http://www.perlmonks.org/?node_id=728569

Cases

Top

HOW TO ADD ANOTHER ONE

Top

Please feel free to add as many checks as you like. This is done in three steps:

Add the test

Add one (or more) tests for this case to t/75-perl-beginner.t

The test should be successful when your supplied sample fails the check and returns the correct error message. As texts of error messages may change, try to match a good part which allows identification of the message but don't match the very exact text.

Tests could use either one-liners written as strings within the test file or external support files. There are samples for both ways in the test script.

Add the check

Add the check to the check-sub of this file (Document/Perl/Beginner.pm). There are plenty samples here. Remember to add a sample (and maybe short description) what would fail the test.

Run the test script to match your test case(s) to the new check.

Add the configuration option

Go to Config.pm, look for the beginner error checks configuration and add a new setting for your new check there. It defaults to 1 (run the check), but a user could turn it off by setting this to 0 within the Padre configuration file.

COPYRIGHT & LICENSE

Top


Padre documentation Contained in the Padre distribution.
package Padre::Document::Perl::Beginner;

use 5.008;
use strict;
use warnings;

our $VERSION = '0.86';


sub new {
	my $class = shift;

	return bless {@_}, $class;
}

sub error {
	return $_[0]->{error};
}

sub _report {
	my $self     = shift;
	my $text     = shift;
	my $prematch = shift;
	my @samples  = @_;

	# The internal _text field is used instead of the document to make sure the
	# tests can also use it. We will also need to separate from the Padre document class
	# so this code can be factored out to a stand alone module

	#my $document = $self->{document};
	#my $editor   = $document->{editor};

	$prematch ||= '';
	my $error_start_position = length($prematch);

	#my $line = $editor->LineFromPosition($error_start_position);
	my @lines = split /[\r\n]/, substr( $self->{_text}, 0, $error_start_position ), -1;
	my $line = @lines || 1;

	#++$line; # Editor starts counting at 0

	# These are two lines to enable the translators to use argument numbers:
	$self->{error} = Wx::gettext( sprintf( 'Line %d: ', $line ) ) . Wx::gettext( sprintf( $text, @_ ) );

	return;
}

sub check {
	my ( $self, $text ) = @_;

	$self->{error} = undef;

	$self->{_text} = $text;

	# Fast exits if there is nothing to check:
	return 1 if !defined($text);
	return 1 if $text eq '';

	my $config = Padre->ide->config;

	# Cut POD parts out of the text
	$text =~ s/(^|[\r\n])(\=(pod|item|head\d)\b.+?[\r\n]\=cut[\r\n])/$1.(" "x(length($2)))/seg;

	# TO DO: Replace all comments by whitespaces, otherwise they could mix up some tests

	if ( $config->lang_perl5_beginner_split and $text =~ m/^([\x00-\xff]*?)split([^;]+);/ ) {
		my $prematch = $1;
		my $cont     = $2;
		if ( $cont =~ m{\@} ) {
			$self->_report( "The second parameter of split is a string, not an array", $prematch );
			return;
		}
	}

	if ( $config->lang_perl5_beginner_warning and $text =~ /^([\x00-\xff]*?)use\s+warning\s*;/ ) {
		$self->_report( "You need to write use warnings (with an s at the end) and not use warning.", $1 );
		return;
	}

	if (    $config->lang_perl5_beginner_map
		and $text =~ /^([\x00-\xff]*?)map[\s\t\r\n]*\{.+?\}[\s\t\r\n]*\(.+?\)[\s\t\r\n]*\,/ )
	{
		$self->_report( "map (),x uses x also as list value for map.", $1 );
		return;
	}

	if ( $config->lang_perl5_beginner_debugger and $text =~ /^([\x00-\xff]*?)package DB[\;\:]/ ) {
		$self->_report( "This file uses the DB-namespace which is used by the Perl Debugger.", $1 );
		return;
	}

	# TO DO: Change this to
	#	if ( $text =~ /[^\{\;][\s\t\r\n]*chomp\b/ ) {
	# as soon as this module could set the cursor to the occurence line
	# because it may trigger a higher amount of false positives.

	# (Ticket #675)

	if ( $config->lang_perl5_beginner_chomp and $text =~ /^([\x00-\xff]*?)(print|[\=\.\,])[\s\t\r\n]*chomp\b/ ) {
		$self->_report( "chomp doesn't return the chomped value, it modifies the variable given as argument.", $1 );
		return;
	}

	if (    $config->lang_perl5_beginner_map2
		and $text =~ /^([\x00-\xff]*?)map[\s\t\r\n]*\{[\s\t\r\n]*(\$_[\s\t\r\n]*\=\~[\s\t\r\n]*)?s\// )
	{
		$self->_report( "Substitute (s///) doesn't return the changed value even if map.", $1 );
		return;
	}

	if ( $config->lang_perl5_beginner_perl6 and $text =~ /^([\x00-\xff]*?)\(\<\@\w+\>\)/ ) {
		$self->_report( "(<\@Foo>) is Perl6 syntax and usually not valid in Perl5.", $1 );
		return;
	}

	# TODO if ( my $x = 23 ) {  should be OK I think, that is when we declare the variable with the if construct

	if (    $config->lang_perl5_beginner_ifsetvar
		and $text =~ m/\A([\x00-\xff]*?  ^[^#]*) if\b  \s*  (  \(\s*  (?<!my)\s*[\$\@\%]\w+  \s*=[^=~]  )/xsm )
	{
		$self->_report( "A single = in a if-condition is usually a typo, use == or eq to compare.", $1 );
		return;
	}

	if ($config->lang_perl5_beginner_pipeopen
		and ( $text
			=~ /^([\x00-\xff]*?)open[\s\t\r\n]*\(?\$?\w+[\s\t\r\n]*(\,.+?)?[\s\t\r\n]*\,[\s\t\r\n]*?([\"\'])(.*?)\|(.*?)\3/
		)
		and ( length($4) > 0 )
		and ( length($5) > 0 )
		)
	{
		$self->_report( "Using a | char in open without a | at the beginning or end is usually a typo.", $1 );
		return;
	}

	if (    $config->lang_perl5_beginner_pipe2open
		and $text =~ /^([\x00-\xff]*?)open[\s\t\r\n]*\(?\$?\w+[\s\t\r\n]*\,(.+?\,)?([\"\'])\|.+?\|\3/ )
	{
		$self->_report( "You can't use open to pipe to and from a command at the same time.", $1 );
		return;
	}

	if ( $config->lang_perl5_beginner_regexq and $text =~ m/^([\x00-\xff]*?)\=\~  [\s\t\r\n]*  \/ \^?  [\+\*\?\{] /xs )
	{
		$self->_report(
			"A regular expression starting with a quantifier ( + * ? { ) doesn't make sense, you may want to escape it with a \\.",
			$1
		);
		return;
	}

	if ( $config->lang_perl5_beginner_elseif and $text =~ /^([\x00-\xff]*?)\belse\s+if\b/ ) {
		$self->_report( "'else if' is wrong syntax, correct if 'elsif'.", $1 );
		return;
	}

	if ( $config->lang_perl5_beginner_elseif and $text =~ /^([\x00-\xff]*?)\belseif\b/ ) {
		$self->_report( "'elseif' is wrong syntax, correct if 'elsif'.", $1 );
		return;
	}

	if ( $config->lang_perl5_beginner_close and $text =~ /^(.*?[^>]?)close;/ ) { # don't match Socket->close;
		$self->_report( "close; usually closes STDIN, STDOUT or something else you don't want.", $1 );
		return;
	}

	return 1;
}

1;

# Copyright 2008-2011 The Padre development team as listed in Padre.pm.
# LICENSE
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl 5 itself.