From 79674b4530e6d213da5e1e3d5647436cfeae1a0d Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 5 Jun 2017 13:30:09 -0400 Subject: [PATCH 5/6] spellchecker for comments v3 Changes in v3: * moved back to spellcheck-enchant.h/.c, and converted to a warning: -Wspellcheck-comments * added "--with-enchant" autoconfery, making the spellchecker code optional, using PKG_CHECK_MODULES to invoke pkg-config * print details about the spellchecking provider info if "-v" was parsed * if there's more than one suggestion, mark their fix-it hints as mutually-incompatible (to keep -fdiagnostics-generate-patch sane) * moved testcases to c-c++-common, and added a dg-require-enchant for skipping them when gcc was not configured with --with-enchant. * documented it within invoke.texi DONE: * ChangeLog TODO: * what about bootstraps? * what about cross-compilation? * test during a bootstrap? * what needs fixing? * what's the signal:noise ratio like? * blurb * reinstate -Wfixme and -Wtodo? Changes in v2: * moved it all to a plugin, within the testsuite * added test coverage * added option -fplugin-arg-spellcheck-show-provider * added option -fplugin-arg-spellcheck-language * fixed bug with misspelling at very end of a comment * fixed bug with apostrophes breaking words * fixed quoting of suggestions * don't spellcheck within system headers * don't spellcheck URLs * removed the -Wfixme and -Wtodo idea config/ChangeLog: * pkg.m4: New file, taken from pkg-config-0.24. gcc/ChangeLog: * Makefile.in (ENCHANT_CFLAGS, ENCHANT_LIBS): Add. (BACKENDLIBS): Add $(ENCHANT_LIBS). (OBJS): Add spellcheck-enchant.o. (CFLAGS-spellcheck-enchant.o): Add $(ENCHANT_CFLAGS). * aclocal.m4: Include ../config/pkg.m4. * config.in: Regenerate. * configure.ac: Add --with-enchant. * doc/invoke.texi (Warning Options): Add -Wspellcheck-comments and -Wspellcheck-comments=. (-Wspellcheck-comments): New option. * spellcheck-enchant.c: New file. * spellcheck-enchant.h: New file. gcc/c-family/ChangeLog: * c-lex.c: Include spellcheck-enchant.h. (init_c_lex): Wire up the spellchecking callback if requested, and configured with --with-enchant. * c-opts.c: Include spellcheck-enchant.h. (c_common_post_options): If -Wspellcheck-comments was requested, call spellcheck_enchant_init, if available. (c_common_finish): Call spellcheck_enchant_finish if available. * c.opt (warn_spellcheck_comments): Mark this as a string. (Wspellcheck-comments): New. (Wspellcheck-comments=): New. gcc/testsuite/ChangeLog: * c-c++-common/Wspellcheck-comments-1.c: New test case. * c-c++-common/Wspellcheck-comments-2.c (UNKNOWN): Likewise. * c-c++-common/Wspellcheck-comments-3.c (UNKNOWN): Likewise. * c-c++-common/Wspellcheck-comments-4.c (UNKNOWN): Likewise. * c-c++-common/Wspellcheck-comments-5.c (UNKNOWN): Likewise. * lib/target-supports-dg.exp (dg-require-enchant): New procedure. * lib/target-supports.exp (check_configured_with_enchant): New procedure. --- config/pkg.m4 | 214 +++++++++++++ gcc/Makefile.in | 9 +- gcc/aclocal.m4 | 1 + gcc/c-family/c-lex.c | 9 + gcc/c-family/c-opts.c | 9 + gcc/c-family/c.opt | 11 + gcc/config.in | 6 + gcc/configure.ac | 14 + gcc/doc/invoke.texi | 22 ++ gcc/spellcheck-enchant.c | 345 +++++++++++++++++++++ gcc/spellcheck-enchant.h | 37 +++ .../c-c++-common/Wspellcheck-comments-1.c | 22 ++ .../c-c++-common/Wspellcheck-comments-2.c | 14 + .../c-c++-common/Wspellcheck-comments-3.c | 21 ++ .../c-c++-common/Wspellcheck-comments-4.c | 5 + .../c-c++-common/Wspellcheck-comments-5.c | 6 + gcc/testsuite/lib/target-supports-dg.exp | 9 + gcc/testsuite/lib/target-supports.exp | 4 + 18 files changed, 757 insertions(+), 1 deletion(-) create mode 100644 config/pkg.m4 create mode 100644 gcc/spellcheck-enchant.c create mode 100644 gcc/spellcheck-enchant.h create mode 100644 gcc/testsuite/c-c++-common/Wspellcheck-comments-1.c create mode 100644 gcc/testsuite/c-c++-common/Wspellcheck-comments-2.c create mode 100644 gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c create mode 100644 gcc/testsuite/c-c++-common/Wspellcheck-comments-4.c create mode 100644 gcc/testsuite/c-c++-common/Wspellcheck-comments-5.c diff --git a/config/pkg.m4 b/config/pkg.m4 new file mode 100644 index 0000000..c5b26b5 --- /dev/null +++ b/config/pkg.m4 @@ -0,0 +1,214 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# serial 1 (pkg-config-0.24) +# +# Copyright © 2004 Scott James Remnant . +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) +m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +# only at the first occurence in configure.ac, so if the first place +# it's called might be skipped (such as if it is within an "if", you +# have to call PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes ], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])# PKG_CHECK_MODULES + + +# PKG_INSTALLDIR(DIRECTORY) +# ------------------------- +# Substitutes the variable pkgconfigdir as the location where a module +# should install pkg-config .pc files. By default the directory is +# $libdir/pkgconfig, but the default can be changed by passing +# DIRECTORY. The user can override through the --with-pkgconfigdir +# parameter. +AC_DEFUN([PKG_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, + [with_pkgconfigdir=]pkg_default) +AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +]) dnl PKG_INSTALLDIR + + +# PKG_NOARCH_INSTALLDIR(DIRECTORY) +# ------------------------- +# Substitutes the variable noarch_pkgconfigdir as the location where a +# module should install arch-independent pkg-config .pc files. By +# default the directory is $datadir/pkgconfig, but the default can be +# changed by passing DIRECTORY. The user can override through the +# --with-noarch-pkgconfigdir parameter. +AC_DEFUN([PKG_NOARCH_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([noarch-pkgconfigdir], + [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, + [with_noarch_pkgconfigdir=]pkg_default) +AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +]) dnl PKG_NOARCH_INSTALLDIR + + +# PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, +# [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# ------------------------------------------- +# Retrieves the value of the pkg-config variable for the given module. +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])# PKG_CHECK_VAR diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 6e0e55a..fd4cce6 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -381,6 +381,10 @@ GMPINC = @GMPINC@ ISLLIBS = @ISLLIBS@ ISLINC = @ISLINC@ +# Enchant +ENCHANT_CFLAGS = @ENCHANT_CFLAGS@ +ENCHANT_LIBS = @ENCHANT_LIBS@ + # Set to 'yes' if the LTO front end is enabled. enable_lto = @enable_lto@ @@ -1051,7 +1055,7 @@ BUILD_LIBDEPS= $(BUILD_LIBIBERTY) LIBS = @LIBS@ libcommon.a $(CPPLIB) $(LIBINTL) $(LIBICONV) $(LIBBACKTRACE) \ $(LIBIBERTY) $(LIBDECNUMBER) $(HOST_LIBS) BACKENDLIBS = $(ISLLIBS) $(GMPLIBS) $(PLUGINLIBS) $(HOST_LIBS) \ - $(ZLIB) + $(ZLIB) $(ENCHANT_LIBS) # Any system libraries needed just for GNAT. SYSLIBS = @GNAT_LIBEXC@ @@ -1455,6 +1459,7 @@ OBJS = \ simplify-rtx.o \ sparseset.o \ spellcheck.o \ + spellcheck-enchant.o \ spellcheck-tree.o \ sreal.o \ stack-ptr-mod.o \ @@ -2060,6 +2065,8 @@ gcc-ranlib$(exeext): gcc-ranlib.o $(AR_OBJS) $(LIBDEPS) +$(LINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) gcc-ranlib.o -o $@ \ $(AR_OBJS) $(LIBS) $(AR_LIBS) +CFLAGS-spellcheck-enchant.o += $(ENCHANT_CFLAGS) + CFLAGS-gcc-ar.o += $(DRIVER_DEFINES) \ -DTARGET_MACHINE=\"$(target_noncanonical)\" \ @TARGET_SYSTEM_ROOT_DEFINE@ -DPERSONALITY=\"ar\" diff --git a/gcc/aclocal.m4 b/gcc/aclocal.m4 index 84f8abb..dd93a0e 100644 --- a/gcc/aclocal.m4 +++ b/gcc/aclocal.m4 @@ -32,6 +32,7 @@ m4_include([../config/lib-prefix.m4]) m4_include([../config/mmap.m4]) m4_include([../config/override.m4]) m4_include([../config/picflag.m4]) +m4_include([../config/pkg.m4]) m4_include([../config/progtest.m4]) m4_include([../config/stdint.m4]) m4_include([../config/warnings.m4]) diff --git a/gcc/c-family/c-lex.c b/gcc/c-family/c-lex.c index e1c8bdf..ed2617c 100644 --- a/gcc/c-family/c-lex.c +++ b/gcc/c-family/c-lex.c @@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see #include "debug.h" #include "attribs.h" +#include "spellcheck-enchant.h" /* We may keep statistics about how long which files took to compile. */ static int header_time, body_time; @@ -82,6 +83,14 @@ init_c_lex (void) cb->has_attribute = c_common_has_attribute; cb->get_source_date_epoch = cb_get_source_date_epoch; cb->get_suggestion = cb_get_suggestion; + if (warn_spellcheck_comments) + { +#ifdef HAVE_ENCHANT + cb->comment = spellcheck_enchant_check_comment; +#else + sorry ("this build of gcc was not configured with libenchant"); +#endif + } /* Set the debug callbacks if we can use them. */ if ((debug_info_level == DINFO_LEVEL_VERBOSE diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c index be4478f..7ebfe01 100644 --- a/gcc/c-family/c-opts.c +++ b/gcc/c-family/c-opts.c @@ -40,6 +40,7 @@ along with GCC; see the file COPYING3. If not see #include "plugin.h" /* For PLUGIN_INCLUDE_FILE event. */ #include "mkdeps.h" #include "dumpfile.h" +#include "spellcheck-enchant.h" #ifndef DOLLARS_IN_IDENTIFIERS # define DOLLARS_IN_IDENTIFIERS true @@ -993,6 +994,10 @@ c_common_post_options (const char **pfilename) } else { +#ifdef HAVE_ENCHANT + if (warn_spellcheck_comments) + spellcheck_enchant_init (warn_spellcheck_comments); +#endif init_c_lex (); /* When writing a PCH file, avoid reading some other PCH file, @@ -1163,6 +1168,10 @@ c_common_finish (void) with cpp_destroy (). */ cpp_finish (parse_in, deps_stream); +#ifdef HAVE_ENCHANT + spellcheck_enchant_finish (); +#endif + if (deps_stream && deps_stream != out_stream && (ferror (deps_stream) || fclose (deps_stream))) fatal_error (input_location, "closing dependency file %s: %m", deps_file); diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 37bb236..d3354b6 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -33,6 +33,9 @@ C++ Language ObjC++ +Variable +char *warn_spellcheck_comments + -all-warnings C ObjC C++ ObjC++ Warning Alias(Wall) @@ -722,6 +725,14 @@ Wsizeof-array-argument C ObjC C++ ObjC++ Var(warn_sizeof_array_argument) Warning Init(1) Warn when sizeof is applied on a parameter declared as an array. +Wspellcheck-comments +C ObjC C++ ObjC++ Warning Alias(Wspellcheck-comments=, en_US, 0) +Warn about words in comments that may be misspelled. + +Wspellcheck-comments= +C ObjC C++ ObjC++ Joined Warning Var(warn_spellcheck_comments) +Warn about words in comments that may be misspelled. + Wstringop-overflow C ObjC C++ ObjC++ Warning Alias(Wstringop-overflow=, 2, 0) Warn about buffer overflow in string manipulation functions like memcpy diff --git a/gcc/config.in b/gcc/config.in index bf2aa7b..9f0f407 100644 --- a/gcc/config.in +++ b/gcc/config.in @@ -1134,6 +1134,12 @@ #endif +/* Define if libenchant is available */ +#ifndef USED_FOR_TARGET +#undef HAVE_ENCHANT +#endif + + /* Define to 1 if you have the header file. */ #ifndef USED_FOR_TARGET #undef HAVE_EXT_HASH_MAP diff --git a/gcc/configure.ac b/gcc/configure.ac index acfe979..ae6e8bf 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -6359,6 +6359,20 @@ if test x"$ld_pushpopstate_support" = xyes; then fi AC_MSG_RESULT($ld_pushpopstate_support) +# Optional spellchecking support, via libenchant + +AC_ARG_WITH([enchant], + AS_HELP_STRING([--with-enchant], + [Build with the Enchant spellchecking library])) + +if test "x$with_enchant" = "xyes"; then + PKG_CHECK_MODULES([ENCHANT], [enchant]) + AC_DEFINE(HAVE_ENCHANT, 1, [Define if libenchant is available]) +fi + +AC_SUBST([ENCHANT_CFLAGS]) +AC_SUBST([ENCHANT_LIBS]) + # Configure the subdirectories # AC_CONFIG_SUBDIRS($subdirs) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 819e800..0714343 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -308,6 +308,7 @@ Objective-C and Objective-C++ Dialects}. -Wsign-compare -Wsign-conversion -Wfloat-conversion @gol -Wno-scalar-storage-order -Wsizeof-pointer-div @gol -Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol +-Wspellcheck-comments -Wspellcheck-comments=@var{language-tag} @gol -Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol -Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol -Wstringop-overflow=@var{n} @gol @@ -4629,6 +4630,27 @@ This warning level also warns about left-shifting 1 into the sign bit, unless C++14 mode is active. @end table +@item -Wspellcheck-comments @r{(C and C++ only)} +@itemx -Wspellcheck-comments=@var{language-tag} @r{(C and C++ only)} +@opindex Wspellcheck-comments +@opindex Wno-spellcheck-comments +Warn about words in comments that may be misspelled. + +This warning is only available if GCC was configured with the Enchant +spellchecking library (via @option{--with-enchant}). + +The option @option{-Wspellcheck-comments=} selects with human language +to use (e.g. @code{en_GB}). + +If enabled via @option{-Wspellcheck-comments}, the warning defaults +to using @code{en_US}. + +If an unrecognized language tag is used, a list of available +spellchecking providers is printed. + +Details of which spellchecking-provider is in use can be seen +via @option{-v}. + @item -Wswitch @opindex Wswitch @opindex Wno-switch diff --git a/gcc/spellcheck-enchant.c b/gcc/spellcheck-enchant.c new file mode 100644 index 0000000..f62965a --- /dev/null +++ b/gcc/spellcheck-enchant.c @@ -0,0 +1,345 @@ +/* Spellchecking using the Enchant "meta-library". + Copyright (C) 2017 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC 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. + +GCC 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 GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "diagnostic.h" +#include "options.h" + +/* This file is guarded by "#ifdef HAVE_ENCHANT". */ + +#ifdef HAVE_ENCHANT + +/* Enchant is LGPL 2.1 "or (at your option) any later version." with + a special exception to allow non-LGPL spellchecking providers. */ + +#include + +/* Class wrapping the result of enchant_dict_suggest, ensuring that + the suggestions are cleaned up. */ + +class suggestions +{ + public: + suggestions (EnchantDict *dict, const char *const word, ssize_t len) + : m_dict (dict), m_suggestions (NULL) + { + m_suggestions = enchant_dict_suggest (m_dict, word, len, &m_num); + } + ~suggestions () + { + enchant_dict_free_string_list (m_dict, m_suggestions); + } + + size_t get_count () const { return m_num; } + const char *get_suggestion (size_t index) const + { + return m_suggestions[index]; + } + + private: + EnchantDict *m_dict; + size_t m_num; + char **m_suggestions; +}; + +/* Callback for describing metadata about the Enchant provider in use. */ + +static void +describe_broker_cb (const char * const provider_name, + const char * const provider_desc, + const char * const provider_dll_file, + void *) +{ + inform (UNKNOWN_LOCATION, + "spellcheck provider: %qs description: %qs file: %qs", + provider_name, provider_desc, provider_dll_file); +} + +/* A class for wrapping access to the Enchant API (both the broker + and the dict). */ + +class checker +{ + public: + checker (const char *language_tag); + ~checker (); + + bool misspelled_p (const char *const word, ssize_t len); + suggestions get_suggestions (const char *const word, ssize_t len); + + static checker *singleton; + + private: + EnchantBroker *m_broker; + EnchantDict *m_dict; +}; + +/* Callback for describing the available languages. */ + +static void +describe_dict_cb (const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void *) +{ + inform (UNKNOWN_LOCATION, + "available language: %qs" + " (provider: %qs description: %qs file: %qs)", + lang_tag, provider_name, provider_desc, provider_file); +} + +/* checker's ctor. */ + +checker::checker (const char *language_tag) +: m_broker (NULL), m_dict (NULL) +{ + m_broker = enchant_broker_init (); + if (!m_broker) + { + error_at (UNKNOWN_LOCATION, "enchant_broker_init failed"); + return; + } + m_dict = enchant_broker_request_dict (m_broker, language_tag); + if (!m_dict) + { + error_at (UNKNOWN_LOCATION, "unknown language tag: %qs", language_tag); + enchant_broker_list_dicts (m_broker, describe_dict_cb, NULL); + } + + if (verbose_flag) + enchant_broker_describe (m_broker, describe_broker_cb, NULL); + + // TODO: will probably want to support local wordlists also? +} + +/* checker's dtor. */ + +checker::~checker () +{ + if (m_dict) + enchant_broker_free_dict (m_broker, m_dict); + if (m_broker) + enchant_broker_free (m_broker); +} + +/* Return true if WORD of length LEN appears to be misspelled. */ + +bool +checker::misspelled_p (const char *const word, ssize_t len) +{ + if (!m_dict) + return false; + + int result = enchant_dict_check (m_dict, word, len); + return result != 0; +} + +/* Get suggestions for misspelled WORD of length LEN. */ + +suggestions +checker::get_suggestions (const char *const word, ssize_t len) +{ + gcc_assert (m_dict); + return suggestions (m_dict, word, len); +} + +/* Singleton instance of checker. */ + +checker *checker::singleton = NULL; + +/* Generate a location for a word of length LEN within ORD_MAP at the + given LINE AND COLUMN. */ + +static location_t +make_word_location (const line_map_ordinary *ord_map, int line, int column, + size_t len) +{ + location_t start_loc + = linemap_position_for_line_and_column (line_table, ord_map, line, column); + location_t end_loc + = linemap_position_for_line_and_column (line_table, ord_map, line, + column + len - 1); + return make_location (start_loc, start_loc, end_loc); +} + +/* Spellcheck the word at WORD of length LEN, with a location + within ORD_MAP at the given LINE AND COLUMN. + Issue warnings and suggestions for possibly misspelled words. */ + +static void +spellcheck_word (const unsigned char *word, size_t len, + const line_map_ordinary *ord_map, int line, int column) +{ + /* Don't bother spellchecking words with underscores or any upper case, + as these may refer to code entities. + TODO: should we also merge in names from code? */ + for (size_t i = 0; i < len; i++) + { + if (word[i] == '_' || ISUPPER (word[i])) + return; + } + + if (!checker::singleton->misspelled_p ((const char *)word, len)) + return; + + location_t word_loc = make_word_location (ord_map, line, column, len); + + if (warning_at (word_loc, OPT_Wspellcheck_comments_, + "possible spelling mistake in comment: %q.*s", + (int)len, word)) + { + suggestions s + = checker::singleton->get_suggestions ((const char *)word, len); + for (size_t i = 0; i < s.get_count (); i++) + { + rich_location richloc (line_table, word_loc); + richloc.add_fixit_replace (s.get_suggestion (i)); + + /* If there's more than one suggestion, then the + suggestions are mutually exclusive. */ + if (s.get_count () > 1) + richloc.fixits_cannot_be_auto_applied (); + + inform_at_rich_loc (&richloc, "suggestion: %qs", + s.get_suggestion (i)); + } + } +} + +/* Initialize this module, using the given LANGUAGE_TAG as the + language to use for spellchecking (e.g. "en_US"). */ + +void +spellcheck_enchant_init (const char *language_tag) +{ + gcc_assert (language_tag); + checker::singleton = new checker (language_tag); +} + +/* Callback for cpp_callbacks::comments for spellchecking comments + using Enchant. */ + +void +spellcheck_enchant_check_comment (cpp_reader *, source_location loc, + const unsigned char *content, size_t len) +{ + /* Don't spellcheck within system headers. */ + if (in_system_header_at (loc)) + return; + + /* Split into words, tracking the location, and call spellcheck_word + on each word. */ + + /* CONTENT contains the opening slash-star (or slash-slash), + and for C-style comments contains the closing star-slash. */ + gcc_assert (len >= 2); + gcc_assert (content[0] == '/'); + gcc_assert (content[1] == '*' || content[1] == '/'); + bool c_style = (content[1] == '*'); + if (c_style) + { + gcc_assert (content[len - 2] == '*'); + gcc_assert (content[len - 1] == '/'); + } + const unsigned char *iter = content + 2; + const unsigned char *after_last_char = content + len; + if (c_style) + after_last_char -= 2; + const unsigned char *word_start = NULL; + + const struct line_map *map = linemap_lookup (line_table, loc); + const line_map_ordinary *ord_map = linemap_check_ordinary (map); + + int column = LOCATION_COLUMN (loc) + 2; + int line = LOCATION_LINE (loc); + int word_start_column = column; + int word_start_line = line; + + bool within_url = false; + while (iter < after_last_char) + { + unsigned char ch = *iter; + if (ISALPHA (ch) || ch == '_' || ch == '\'') + { + if (!word_start) + { + /* We have the start of a word. */ + word_start = iter; + word_start_column = column; + word_start_line = line; + } + } + else + { + if (word_start) + { + /* We have a word ending. */ + size_t word_len = iter - word_start; + /* Don't check any comments that appear to contain DejaGnu + directives. */ + if (word_len >= 2) + if (0 == strncmp ((const char *)word_start, "dg", 2)) + return; + + /* Detect things that look like URLs. */ + /* "www." */ + if (word_len == 3 && ch == '.') + if (0 == strncmp ((const char *)word_start, "www", 3)) + within_url = true; + + /* "http:" */ + if (word_len == 4 && ch == ':') + if (0 == strncmp ((const char *)word_start, "http", 4)) + within_url = true; + + if (!within_url) + spellcheck_word (word_start, word_len, ord_map, + word_start_line, word_start_column); + word_start = NULL; + } + } + if (ISSPACE (ch)) + within_url = false; + if (ch == '\n') + { + column = 1; + line++; + } + else + column++; + iter++; + } + if (word_start) + if (!within_url) + spellcheck_word (word_start, iter - word_start, ord_map, + word_start_line, word_start_column); +} + +/* Clean up this module. */ + +void +spellcheck_enchant_finish (void) +{ + delete checker::singleton; +} + +#endif /* #ifdef HAVE_ENCHANT */ diff --git a/gcc/spellcheck-enchant.h b/gcc/spellcheck-enchant.h new file mode 100644 index 0000000..ac069f7 --- /dev/null +++ b/gcc/spellcheck-enchant.h @@ -0,0 +1,37 @@ +/* Spellchecking using the Enchant "meta-library". + Copyright (C) 2017 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC 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. + +GCC 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 GCC; see the file COPYING3. If not see +. */ + +#ifndef GCC_SPELLCHECK_ENCHANT_H +#define GCC_SPELLCHECK_ENCHANT_H + +#ifdef HAVE_ENCHANT + +extern void spellcheck_enchant_init (const char *language_tag); + +/* Callback for cpp_callbacks::comments for spellchecking comments + using Enchant. */ + +extern void spellcheck_enchant_check_comment (cpp_reader *, source_location, + const unsigned char *, size_t); + +extern void spellcheck_enchant_finish (void); + +#endif /* #ifdef HAVE_ENCHANT */ + +#endif /* GCC_SPELLCHECK_ENCHANT_H */ diff --git a/gcc/testsuite/c-c++-common/Wspellcheck-comments-1.c b/gcc/testsuite/c-c++-common/Wspellcheck-comments-1.c new file mode 100644 index 0000000..690fd66 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wspellcheck-comments-1.c @@ -0,0 +1,22 @@ +/* { dg-require-enchant "" } */ +/* { dg-options "-Wspellcheck-comments" } */ + +/* When NONCONST_PRED is false the code will evaulate to constant. */ +/* { dg-warning "46: possible spelling mistake in comment: 'evaulate'" "" { target *-*-* } .-1 } */ +/* { dg-message "46: suggestion: 'evaluate'" "" { target *-*-* } .-2 } */ +/* There may be other suggestions in each of these cases. */ + +// Example of a boogus C++-style comment +/* { dg-warning "17: possible spelling mistake in comment: 'boogus'" "" { target *-*-* } .-1 } */ +/* { dg-message "17: suggestion: 'bogus'" "" { target *-*-* } .-2 } */ + +/* Exercise the code path for the final word in a commment*/ +/* { dg-warning "51: possible spelling mistake in comment: 'commment'" "" { target *-*-* } .-1 } */ +/* { dg-message "51: suggestion: 'comment'" "" { target *-*-* } .-2 } */ + +// Exercise the code path for the final word in a C++ commment +/* { dg-warning "55: possible spelling mistake in comment: 'commment'" "" { target *-*-* } .-1 } */ +/* { dg-message "55: suggestion: 'comment'" "" { target *-*-* } .-2 } */ + +/* Examples of words that are correctly spelled, containing apostrophes: + don't doesn't is isn't. */ diff --git a/gcc/testsuite/c-c++-common/Wspellcheck-comments-2.c b/gcc/testsuite/c-c++-common/Wspellcheck-comments-2.c new file mode 100644 index 0000000..10c1825 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wspellcheck-comments-2.c @@ -0,0 +1,14 @@ +/* { dg-require-enchant "" } */ +/* { dg-options "-Wspellcheck-comments -fdiagnostics-show-caret" } */ + +/* Verify that misspelled words are underlined correctly. We need + a word that the checker won't offer a suggestion for, to avoid + having to specify checker output within the test case. + Hence the use of splendiferouslitudinalistic here. */ +#if 0 +{ dg-warning "possible spelling mistake in comment: 'splendiferouslitudinalistic'" "" { target *-*-* } .-2 } +{ dg-begin-multiline-output "" } + Hence the use of splendiferouslitudinalistic here. + ^~~~~~~~~~~~~~~~~~~~~~~~~~~ +{ dg-end-multiline-output "" } +#endif diff --git a/gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c b/gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c new file mode 100644 index 0000000..385f84c --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c @@ -0,0 +1,21 @@ +/* Verify that we don't complain about our legal boilerplate. */ +/* { dg-require-enchant "" } */ +/* { dg-options "-Wspellcheck-comments" } */ + +/* Copyright (C) 2017 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC 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. + +GCC 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 GCC; see the file COPYING3. If not see +. */ diff --git a/gcc/testsuite/c-c++-common/Wspellcheck-comments-4.c b/gcc/testsuite/c-c++-common/Wspellcheck-comments-4.c new file mode 100644 index 0000000..02a64f4 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wspellcheck-comments-4.c @@ -0,0 +1,5 @@ +/* { dg-require-enchant "" } */ +/* { dg-options "-Wspellcheck-comments=this_is_not_valid" } */ +// { dg-error "unknown language tag: 'this_is_not_valid'" "" { target *-*-* } 0 } + +/* A test comment. */ diff --git a/gcc/testsuite/c-c++-common/Wspellcheck-comments-5.c b/gcc/testsuite/c-c++-common/Wspellcheck-comments-5.c new file mode 100644 index 0000000..0f6fa8e --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wspellcheck-comments-5.c @@ -0,0 +1,6 @@ +/* Example of specifying language. */ +/* { dg-require-enchant "" } */ +/* { dg-options "-Wspellcheck-comments=en_GB" } */ +// Is it color or colour? +// { dg-warning "possible spelling mistake in comment: 'color'" "" { target *-*-* } .-1 } +// { dg-message "suggestion: 'colour'" "" { target *-*-* } .-2 } diff --git a/gcc/testsuite/lib/target-supports-dg.exp b/gcc/testsuite/lib/target-supports-dg.exp index 6400d64..1648436 100644 --- a/gcc/testsuite/lib/target-supports-dg.exp +++ b/gcc/testsuite/lib/target-supports-dg.exp @@ -265,6 +265,15 @@ proc dg-require-linker-plugin { args } { } } +# If this host does not support enchant, skip this test. + +proc dg-require-enchant { args } { + if { ![check_configured_with_enchant] } { + upvar dg-do-what dg-do-what + set dg-do-what [list [lindex ${dg-do-what} 0] "N" "P"] + } +} + # Add any target-specific flags needed for accessing the given list # of features. This must come after all dg-options. diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp index ded6383..b23ff0e 100644 --- a/gcc/testsuite/lib/target-supports.exp +++ b/gcc/testsuite/lib/target-supports.exp @@ -8391,3 +8391,7 @@ proc check_effective_target_arm_coproc4_ok { } { return [check_cached_effective_target arm_coproc4_ok \ check_effective_target_arm_coproc4_ok_nocache] } + +proc check_configured_with_enchant {} { + return [check_configured_with "-with-enchant"] +} -- 1.8.5.3