From 18ed7c0a41278597b56285e551829d47d15b3827 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 11:14:17 -0600 Subject: [PATCH 01/17] Update wro4j version to 1.8.0 and change gson scope to compile --- pom.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7234f9a..0c1e353 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ jar wro4j runner - 1.7.7 + 1.8.0 ro.isdc.wro.runner.Wro4jCommandLineRunner @@ -29,7 +29,7 @@ com.google.code.gson gson - provided + compile org.jruby @@ -83,6 +83,12 @@ slf4j-log4j12 compile + + org.codehaus.groovy + groovy-all + 2.1.6 + compile + From e0476b2c88169ff26ca45f1b4765dcd2c3d04821 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 11:16:01 -0600 Subject: [PATCH 02/17] Refactor RunnerJsHintProcessor to enhance resource file handling and fixed issue with .jshintrc not loading --- .../processor/RunnerJsHintProcessor.java | 149 ++++++++++++++++-- 1 file changed, 137 insertions(+), 12 deletions(-) diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index 567c3f1..5a8d613 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -1,22 +1,28 @@ package ro.isdc.wro.runner.processor; import ro.isdc.wro.extensions.processor.js.JsHintProcessor; +import ro.isdc.wro.extensions.processor.support.linter.AbstractLinter; import ro.isdc.wro.extensions.processor.support.linter.LinterException; import ro.isdc.wro.model.resource.Resource; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.Map; -/** - * Custom extension of {@link JsHintProcessor} created for wro4j-runner. - * - * @author Alex Objelean - * @since 1.7.2 - */ -public class RunnerJsHintProcessor - extends JsHintProcessor { - /** - * Override the alias of original jsHint processor implementation. - */ +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; + +public class RunnerJsHintProcessor extends JsHintProcessor { public static String ALIAS = JsHintProcessor.ALIAS; + + public RunnerJsHintProcessor() { + super(); + } + @Override protected void onLinterException(final LinterException e, final Resource resource) { super.onLinterException(e, resource); @@ -24,4 +30,123 @@ protected void onLinterException(final LinterException e, final Resource resourc System.err.println(e.getErrors()); throw e; } -} + + private File getResourceFile(Resource resource) { + if (resource == null || resource.getUri() == null) { + return null; + } + File file = new File(resource.getUri()); + if (file.exists()) { + System.out.println("[JSHint][DEBUG] getResourceFile: Encontrado por ruta directa: " + file.getAbsolutePath()); + return file; + } + String contextFolder = System.getProperty("contextFolder"); + if (contextFolder != null) { + String relativePath = resource.getUri().replaceFirst("^/", ""); + file = new File(contextFolder, relativePath); + if (file.exists()) { + System.out.println("[JSHint][DEBUG] getResourceFile: Encontrado relativo a contextFolder: " + file.getAbsolutePath()); + return file; + } + } else { + System.out.println("[JSHint][DEBUG] contextFolder no definido en System properties"); + } + System.out.println("[JSHint][DEBUG] getResourceFile: No se encontró archivo para resource.getUri(): " + resource.getUri()); + return null; + } + + /** + * Busca .jshintrc desde el directorio inicial hacia arriba hasta contextFolder o la raíz. + */ + private Map findAndLoadJshintrc(File startDir) { + File dir = startDir.getAbsoluteFile(); // <-- Asegura que sea absoluto + while (dir != null) { + File jshintrc = new File(dir, ".jshintrc"); + System.out.println("[JSHint][DEBUG] Revisando: " + jshintrc.getAbsolutePath()); + if (jshintrc.exists()) { + FileReader reader = null; + try { + reader = new FileReader(jshintrc); + Gson gson = new Gson(); + Type type = new TypeToken>(){}.getType(); + System.out.println("[JSHint][DEBUG] .jshintrc encontrado y cargado: " + jshintrc.getAbsolutePath()); + return gson.fromJson(reader, type); + } catch (IOException e) { + System.err.println("No se pudo leer .jshintrc en " + jshintrc.getAbsolutePath() + ": " + e.getMessage()); + break; + } finally { + if (reader != null) { + try { reader.close(); } catch (IOException ignore) {} + } + } + } + dir = dir.getParentFile(); + System.out.println("[JSHint][DEBUG] Subiendo a directorio padre: " + (dir != null ? dir.getAbsolutePath() : "null")); + } + return null; + } + + // Convierte el Map de opciones a CSV: key=value,key2=value2 + private String mapToCsvOptions(Map map) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + if (sb.length() > 0) sb.append(","); + sb.append(entry.getKey()).append("=").append(entry.getValue()); + } + return sb.toString(); + } + + @Override + public void process(final Resource resource, final Reader reader, final Writer writer) + throws IOException { + System.out.println("[JSHint][DEBUG] Iniciando process para recurso: " + (resource != null ? resource.getUri() : "null")); + final String content = org.apache.commons.io.IOUtils.toString(reader); + final AbstractLinter linter = newLinter(); + try { + File resourceFile = getResourceFile(resource); + System.out.println("[JSHint][DEBUG] resourceFile: " + (resourceFile != null ? resourceFile.getAbsolutePath() : "null")); + String options = null; + File searchDir = null; + if (resourceFile != null && resourceFile.exists()) { + searchDir = resourceFile.getParentFile(); + } else { + String contextFolder = System.getProperty("contextFolder"); + if (contextFolder != null) { + searchDir = new File(contextFolder).getAbsoluteFile(); // <-- AQUÍ + } + } + if (searchDir != null) { + System.out.println("[JSHint][DEBUG] Buscando .jshintrc desde: " + searchDir.getAbsolutePath()); + Map optionsMap = findAndLoadJshintrc(searchDir); + if (optionsMap != null) { + options = mapToCsvOptions(optionsMap); + System.out.println("[JSHint] Usando configuración de .jshintrc encontrada en jerarquía."); + System.out.println("[JSHint][DEBUG] Opciones CSV: " + options); + } else { + System.out.println("[JSHint] No se encontró .jshintrc en la jerarquía para: " + searchDir.getAbsolutePath()); + } + } else { + System.out.println("[JSHint][DEBUG] searchDir es null, no se buscará .jshintrc"); + } + if (options == null) { + options = createDefaultOptions(); + System.out.println("[JSHint][DEBUG] Usando opciones por defecto: " + options); + } + linter.setOptions(options).validate(content); + } catch (final LinterException e) { + System.out.println("[JSHint][DEBUG] LinterException capturada: " + e.getMessage()); + onLinterException(e, resource); + } catch (final ro.isdc.wro.WroRuntimeException e) { + System.out.println("[JSHint][DEBUG] WroRuntimeException capturada: " + e.getMessage()); + onException(e); + final String resourceUri = resource == null ? "" : "[" + resource.getUri() + "]"; + System.err.println("Exception while applying " + getClass().getSimpleName() + " processor on the " + resourceUri + + " resource, no processing applied..."); + } finally { + writer.write(content); + reader.close(); + writer.close(); + System.out.println("[JSHint][DEBUG] Finalizó process para recurso: " + (resource != null ? resource.getUri() : "null")); + } + } +} \ No newline at end of file From 5ccd1ee978ec6efc1af33bb73c385444a2928991 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 11:42:05 -0600 Subject: [PATCH 03/17] Refactor debug logging in RunnerJsHintProcessor for clarity and consistency --- .../processor/RunnerJsHintProcessor.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index 5a8d613..922f8a7 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -37,7 +37,6 @@ private File getResourceFile(Resource resource) { } File file = new File(resource.getUri()); if (file.exists()) { - System.out.println("[JSHint][DEBUG] getResourceFile: Encontrado por ruta directa: " + file.getAbsolutePath()); return file; } String contextFolder = System.getProperty("contextFolder"); @@ -45,13 +44,12 @@ private File getResourceFile(Resource resource) { String relativePath = resource.getUri().replaceFirst("^/", ""); file = new File(contextFolder, relativePath); if (file.exists()) { - System.out.println("[JSHint][DEBUG] getResourceFile: Encontrado relativo a contextFolder: " + file.getAbsolutePath()); return file; } } else { - System.out.println("[JSHint][DEBUG] contextFolder no definido en System properties"); - } - System.out.println("[JSHint][DEBUG] getResourceFile: No se encontró archivo para resource.getUri(): " + resource.getUri()); + System.out.println("[JSHint] contextFolder not defined in System properties"); + } + System.out.println("[JSHint] getResourceFile: No file found for resource.getUri(): " + resource.getUri()); return null; } @@ -62,17 +60,16 @@ private Map findAndLoadJshintrc(File startDir) { File dir = startDir.getAbsoluteFile(); // <-- Asegura que sea absoluto while (dir != null) { File jshintrc = new File(dir, ".jshintrc"); - System.out.println("[JSHint][DEBUG] Revisando: " + jshintrc.getAbsolutePath()); if (jshintrc.exists()) { FileReader reader = null; try { reader = new FileReader(jshintrc); Gson gson = new Gson(); Type type = new TypeToken>(){}.getType(); - System.out.println("[JSHint][DEBUG] .jshintrc encontrado y cargado: " + jshintrc.getAbsolutePath()); + System.out.println("[JShint] .jshintrc loaded: " + jshintrc.getAbsolutePath()); return gson.fromJson(reader, type); } catch (IOException e) { - System.err.println("No se pudo leer .jshintrc en " + jshintrc.getAbsolutePath() + ": " + e.getMessage()); + System.err.println("[JShint] No se pudo leer .jshintrc en " + jshintrc.getAbsolutePath() + ": " + e.getMessage()); break; } finally { if (reader != null) { @@ -81,7 +78,6 @@ private Map findAndLoadJshintrc(File startDir) { } } dir = dir.getParentFile(); - System.out.println("[JSHint][DEBUG] Subiendo a directorio padre: " + (dir != null ? dir.getAbsolutePath() : "null")); } return null; } @@ -99,12 +95,12 @@ private String mapToCsvOptions(Map map) { @Override public void process(final Resource resource, final Reader reader, final Writer writer) throws IOException { - System.out.println("[JSHint][DEBUG] Iniciando process para recurso: " + (resource != null ? resource.getUri() : "null")); + String resourceUri = (resource != null ? resource.getUri() : "null"); + System.out.println("Processing resource: " + resourceUri); final String content = org.apache.commons.io.IOUtils.toString(reader); final AbstractLinter linter = newLinter(); try { File resourceFile = getResourceFile(resource); - System.out.println("[JSHint][DEBUG] resourceFile: " + (resourceFile != null ? resourceFile.getAbsolutePath() : "null")); String options = null; File searchDir = null; if (resourceFile != null && resourceFile.exists()) { @@ -112,41 +108,39 @@ public void process(final Resource resource, final Reader reader, final Writer w } else { String contextFolder = System.getProperty("contextFolder"); if (contextFolder != null) { - searchDir = new File(contextFolder).getAbsoluteFile(); // <-- AQUÍ + searchDir = new File(contextFolder).getAbsoluteFile(); } } if (searchDir != null) { - System.out.println("[JSHint][DEBUG] Buscando .jshintrc desde: " + searchDir.getAbsolutePath()); Map optionsMap = findAndLoadJshintrc(searchDir); if (optionsMap != null) { options = mapToCsvOptions(optionsMap); - System.out.println("[JSHint] Usando configuración de .jshintrc encontrada en jerarquía."); - System.out.println("[JSHint][DEBUG] Opciones CSV: " + options); + System.out.println("[JShint] Using .jshintrc configuration in: " + searchDir.getAbsolutePath()); + System.out.println("[JShint] Options: " + options); } else { - System.out.println("[JSHint] No se encontró .jshintrc en la jerarquía para: " + searchDir.getAbsolutePath()); + System.out.println("[JShint] No .jshintrc found in the hierarchy starting from: " + searchDir.getAbsolutePath()); } } else { - System.out.println("[JSHint][DEBUG] searchDir es null, no se buscará .jshintrc"); + System.out.println("[JShint] Could not determine the directory to search for .jshintrc"); } if (options == null) { options = createDefaultOptions(); - System.out.println("[JSHint][DEBUG] Usando opciones por defecto: " + options); + System.out.println("[JShint] using default options: " + options); } linter.setOptions(options).validate(content); } catch (final LinterException e) { - System.out.println("[JSHint][DEBUG] LinterException capturada: " + e.getMessage()); + System.err.println("LinterException in " + resourceUri + ": " + e.getMessage()); onLinterException(e, resource); } catch (final ro.isdc.wro.WroRuntimeException e) { - System.out.println("[JSHint][DEBUG] WroRuntimeException capturada: " + e.getMessage()); + System.err.println("WroRuntimeException in " + resourceUri + ": " + e.getMessage()); onException(e); - final String resourceUri = resource == null ? "" : "[" + resource.getUri() + "]"; - System.err.println("Exception while applying " + getClass().getSimpleName() + " processor on the " + resourceUri - + " resource, no processing applied..."); + System.err.println("Exception while applying " + getClass().getSimpleName() + " processor on the [" + resourceUri + + "] resource, no processing applied..."); } finally { writer.write(content); reader.close(); writer.close(); - System.out.println("[JSHint][DEBUG] Finalizó process para recurso: " + (resource != null ? resource.getUri() : "null")); + System.out.println("Finished processing: " + resourceUri); } } } \ No newline at end of file From c0ba261f1fbfb698ed7e54ecca344ddc1a309307 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 15:13:17 -0600 Subject: [PATCH 04/17] Refactor RunnerJsHintProcessor for improved error handling and logging --- .../wro/runner/Wro4jCommandLineRunner.java | 706 +++++++++--------- .../processor/RunnerJsHintProcessor.java | 251 ++++--- 2 files changed, 497 insertions(+), 460 deletions(-) diff --git a/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java b/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java index 177330c..12a262f 100644 --- a/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java +++ b/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java @@ -58,7 +58,6 @@ import ro.isdc.wro.util.StopWatch; import ro.isdc.wro.util.io.UnclosableBufferedInputStream; - /** * Default command line runner. Interprets arguments and perform a processing. * @@ -66,362 +65,373 @@ * @since 1.3.4 */ public class Wro4jCommandLineRunner { - private static final Logger LOG = LoggerFactory.getLogger(Wro4jCommandLineRunner.class); - private static String userDirectory = System.getProperty("user.dir"); - private final File defaultWroFile = newDefaultWroFile(); - - @Option(name = "-m", aliases = { - "--minimize" - }, usage = "Turns on the minimization by applying compressor") - private boolean minimize; - @Option(name = "--parallel", usage = "Turns on the parallel preProcessing of resources. This value is false by default.") - private boolean parallelPreprocessing; - @Option(name = "--targetGroups", metaVar = "GROUPS", usage = "Comma separated value of the group names from wro.xml to process. If none is provided, all groups will be processed.") - private String targetGroups; - @Option(name = "-i", aliases = { - "--ignoreMissingResources" - }, usage = "Ignores missing resources") - private boolean ignoreMissingResources; - @Option(name = "--wroFile", metaVar = "PATH_TO_WRO_XML", usage = "The path to the wro model file. By default the model is searched inse the user current folder.") - private final File wroFile = defaultWroFile; - @Option(name = "--wroConfigurationFile", metaVar = "PATH_TO_WRO_PROPERTIES", usage = "The path to the wro.properties file. By default the configuration file is searched inse the user current folder.") - private final File wroConfigurationFile = newWroConfigurationFile(); - @Option(name = "--contextFolder", metaVar = "PATH", usage = "Folder used as a root of the context relative resources. By default this is the user current folder.") - private final File contextFolder = new File(System.getProperty("user.dir")); - @Option(name = "--destinationFolder", metaVar = "PATH", usage = "Where to store the processed result. By default uses the folder named [wro].") - private File destinationFolder = new File(System.getProperty("user.dir"), "wro"); - @Option(name = "-c", aliases = { - "--compressor", "--preProcessors" - }, metaVar = "COMPRESSOR", usage = "Comma separated list of pre-processors") - private String preProcessorsList; - @Option(name = "--postProcessors", metaVar = "POST_PROCESSOR", usage = "Comma separated list of post-processors") - private String postProcessorsList; - - private Properties wroConfigurationAsProperties; - - public static void main(final String[] args) - throws Exception { - new Wro4jCommandLineRunner().doMain(args); - } - - /** - * @return the location where wro file is located by default. Default implementation uses current user directory. - * @VisibleForTesting - */ - protected File newDefaultWroFile() { - return new File(userDirectory, "wro.xml"); - } - - /** - * @return the location where wro configuration file is located by default. Default implementation uses current user - * directory. - * @VisibleForTesting - */ - protected File newWroConfigurationFile() { - return new File(userDirectory, "wro.properties"); - } - - /** - * @return the context folder used by runner. - * @VisibleForTesting - */ - protected File getContextFolder() { - return contextFolder; - } - - /** - * @return the destination folder where the result will be written. - * @VisibleForTesting - */ - protected File getDestinationFolder() { - return contextFolder; - } - - /** - * @param args - */ - protected void doMain(final String[] args) { - LOG.debug("arguments: " + Arrays.toString(args)); - final CmdLineParser parser = new CmdLineParser(this); - parser.setUsageWidth(100); - final StopWatch watch = new StopWatch(); - watch.start("processing"); - try { - parser.parseArgument(args); - LOG.debug("Options: {}", this); - process(); - } catch (final Exception e) { - e.printStackTrace(); - System.err.println(e.getMessage() + "\n\n"); - System.err.println("======================================="); - System.err.println("USAGE"); - System.err.println("======================================="); - parser.printUsage(System.err); - onRunnerException(e); - } finally { - watch.stop(); - LOG.debug(watch.prettyPrint()); - LOG.info("Processing took: {}ms", watch.getLastTaskTimeMillis()); + private static final Logger LOG = LoggerFactory.getLogger(Wro4jCommandLineRunner.class); + private static String userDirectory = System.getProperty("user.dir"); + private final File defaultWroFile = newDefaultWroFile(); + + @Option(name = "-m", aliases = { + "--minimize" + }, usage = "Turns on the minimization by applying compressor") + private boolean minimize; + @Option(name = "--parallel", usage = "Turns on the parallel preProcessing of resources. This value is false by default.") + private boolean parallelPreprocessing; + @Option(name = "--targetGroups", metaVar = "GROUPS", usage = "Comma separated value of the group names from wro.xml to process. If none is provided, all groups will be processed.") + private String targetGroups; + @Option(name = "-i", aliases = { + "--ignoreMissingResources" + }, usage = "Ignores missing resources") + private boolean ignoreMissingResources; + @Option(name = "--wroFile", metaVar = "PATH_TO_WRO_XML", usage = "The path to the wro model file. By default the model is searched inse the user current folder.") + private final File wroFile = defaultWroFile; + @Option(name = "--wroConfigurationFile", metaVar = "PATH_TO_WRO_PROPERTIES", usage = "The path to the wro.properties file. By default the configuration file is searched inse the user current folder.") + private final File wroConfigurationFile = newWroConfigurationFile(); + @Option(name = "--contextFolder", metaVar = "PATH", usage = "Folder used as a root of the context relative resources. By default this is the user current folder.") + private final File contextFolder = new File(System.getProperty("user.dir")); + @Option(name = "--destinationFolder", metaVar = "PATH", usage = "Where to store the processed result. By default uses the folder named [wro].") + private File destinationFolder = new File(System.getProperty("user.dir"), "wro"); + @Option(name = "-c", aliases = { + "--compressor", "--preProcessors" + }, metaVar = "COMPRESSOR", usage = "Comma separated list of pre-processors") + private String preProcessorsList; + @Option(name = "--postProcessors", metaVar = "POST_PROCESSOR", usage = "Comma separated list of post-processors") + private String postProcessorsList; + + private Properties wroConfigurationAsProperties; + + public static void main(final String[] args) + throws Exception { + new Wro4jCommandLineRunner().doMain(args); + } + + /** + * @return the location where wro file is located by default. Default + * implementation uses current user directory. + * @VisibleForTesting + */ + protected File newDefaultWroFile() { + return new File(userDirectory, "wro.xml"); } - } - - /** - * Exception handler. - */ - protected void onRunnerException(final Exception e) { - System.out.println(e.getMessage()); - System.exit(1); // non-zero exit code indicates there was an error - } - - private void process() { - try { - Context.set(Context.standaloneContext()); - // create destinationFolder if needed - if (!destinationFolder.exists()) { - destinationFolder.mkdirs(); - } - final Collection groupsAsList = getTargetGroupsAsList(); - for (final String group : groupsAsList) { - for (final ResourceType resourceType : ResourceType.values()) { - final String groupWithExtension = group + "." + resourceType.name().toLowerCase(); - processGroup(groupWithExtension, destinationFolder); + + /** + * @return the location where wro configuration file is located by default. + * Default implementation uses current user + * directory. + * @VisibleForTesting + */ + protected File newWroConfigurationFile() { + return new File(userDirectory, "wro.properties"); + } + + /** + * @return the context folder used by runner. + * @VisibleForTesting + */ + protected File getContextFolder() { + return contextFolder; + } + + /** + * @return the destination folder where the result will be written. + * @VisibleForTesting + */ + protected File getDestinationFolder() { + return contextFolder; + } + + /** + * @param args + */ + protected void doMain(final String[] args) { + LOG.debug("arguments: " + Arrays.toString(args)); + final CmdLineParser parser = new CmdLineParser(this); + parser.setUsageWidth(100); + final StopWatch watch = new StopWatch(); + watch.start("processing"); + try { + parser.parseArgument(args); + LOG.debug("Options: {}", this); + process(); + } catch (final Exception e) { + e.printStackTrace(); + System.err.println(e.getMessage() + "\n\n"); + System.err.println("======================================="); + System.err.println("USAGE"); + System.err.println("======================================="); + parser.printUsage(System.err); + onRunnerException(e); + } finally { + watch.stop(); + LOG.debug(watch.prettyPrint()); + LOG.info("Processing took: {}ms", watch.getLastTaskTimeMillis()); + } + } + + /** + * Exception handler. + */ + protected void onRunnerException(final Exception e) { + System.out.println(e.getMessage()); + System.exit(1); // non-zero exit code indicates there was an error + } + + private void process() { + try { + Context.set(Context.standaloneContext()); + // create destinationFolder if needed + if (!destinationFolder.exists()) { + destinationFolder.mkdirs(); + } + final Collection groupsAsList = getTargetGroupsAsList(); + for (final String group : groupsAsList) { + for (final ResourceType resourceType : ResourceType.values()) { + final String groupWithExtension = group + "." + resourceType.name().toLowerCase(); + processGroup(groupWithExtension, destinationFolder); + } + } + } catch (final IOException e) { + System.err.println(e.getMessage()); + } + } + + /** + * @return a list containing all groups needs to be processed. + */ + private List getTargetGroupsAsList() + throws IOException { + if (targetGroups == null) { + final WroModel model = getManagerFactory().create().getModelFactory().create(); + return new WroModelInspector(model).getGroupNames(); + } + return Arrays.asList(targetGroups.split(",")); + } + + /** + * Process a single group. + * + * @throws IOException + * if any IO related exception occurs. + */ + private void processGroup(final String group, final File parentFoder) + throws IOException { + final ByteArrayOutputStream resultOutputStream = new ByteArrayOutputStream(); + InputStream resultInputStream = null; + try { + LOG.info("processing group: " + group); + initContext(group, resultOutputStream); + doProcess(); + + // encode version & write result to file + resultInputStream = new UnclosableBufferedInputStream(resultOutputStream.toByteArray()); + final File destinationFile = new File(parentFoder, rename(group, resultInputStream)); + destinationFile.createNewFile(); + // allow the same stream to be read again + resultInputStream.reset(); + LOG.debug("Created file: {}", destinationFile.getName()); + + final OutputStream fos = new FileOutputStream(destinationFile); + // use reader to detect encoding + IOUtils.copy(resultInputStream, fos); + fos.close(); + // delete empty files + if (destinationFile.length() == 0) { + LOG.debug("No content found for group: {}", group); + destinationFile.delete(); + } else { + LOG.info("file size: {} -> {}bytes", destinationFile.getName(), destinationFile.length()); + LOG.info("{} ({}bytes) has been created!", destinationFile.getAbsolutePath(), destinationFile.length()); + } + } finally { + if (resultOutputStream != null) { + resultOutputStream.close(); + } + if (resultInputStream != null) { + resultInputStream.close(); + } } - } - } catch (final IOException e) { - System.err.println(e.getMessage()); } - } - - /** - * @return a list containing all groups needs to be processed. - */ - private List getTargetGroupsAsList() - throws IOException { - if (targetGroups == null) { - final WroModel model = getManagerFactory().create().getModelFactory().create(); - return new WroModelInspector(model).getGroupNames(); + + /** + * Initialize the context for standalone execution. + */ + private void initContext(final String group, final ByteArrayOutputStream resultOutputStream) + throws IOException { + // mock request + final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getRequestURI()).thenReturn(group); + // mock response + final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(resultOutputStream)); + + // init context + Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), initWroConfiguration()); + Context.get().setAggregatedFolderPath(computeAggregatedFolderPath()); + } + + /** + * Perform actual processing by delegating the process call to + * {@link WroManager}. + * + * @throws IOException + * @VisibleForTesting + */ + void doProcess() + throws IOException { + // perform processing + getManagerFactory().create().process(); } - return Arrays.asList(targetGroups.split(",")); - } - - /** - * Process a single group. - * - * @throws IOException - * if any IO related exception occurs. - */ - private void processGroup(final String group, final File parentFoder) - throws IOException { - final ByteArrayOutputStream resultOutputStream = new ByteArrayOutputStream(); - InputStream resultInputStream = null; - try { - LOG.info("processing group: " + group); - initContext(group, resultOutputStream); - doProcess(); - - // encode version & write result to file - resultInputStream = new UnclosableBufferedInputStream(resultOutputStream.toByteArray()); - final File destinationFile = new File(parentFoder, rename(group, resultInputStream)); - destinationFile.createNewFile(); - // allow the same stream to be read again - resultInputStream.reset(); - LOG.debug("Created file: {}", destinationFile.getName()); - - final OutputStream fos = new FileOutputStream(destinationFile); - // use reader to detect encoding - IOUtils.copy(resultInputStream, fos); - fos.close(); - // delete empty files - if (destinationFile.length() == 0) { - LOG.debug("No content found for group: {}", group); - destinationFile.delete(); - } else { - LOG.info("file size: {} -> {}bytes", destinationFile.getName(), destinationFile.length()); - LOG.info("{} ({}bytes) has been created!", destinationFile.getAbsolutePath(), destinationFile.length()); - } - } finally { - if (resultOutputStream != null) { - resultOutputStream.close(); - } - if (resultInputStream != null) { - resultInputStream.close(); - } + + private WroConfiguration initWroConfiguration() + throws IOException { + final PropertyWroConfigurationFactory factory = new PropertyWroConfigurationFactory( + getWroConfigurationProperties()); + final WroConfiguration config = factory.create(); + // keep backward compatibility configuration of some config properties + config.setParallelPreprocessing(parallelPreprocessing); + return config; } - } - - /** - * Initialize the context for standalone execution. - */ - private void initContext(final String group, final ByteArrayOutputStream resultOutputStream) - throws IOException { - // mock request - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - Mockito.when(request.getRequestURI()).thenReturn(group); - // mock response - final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); - Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(resultOutputStream)); - - // init context - Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), initWroConfiguration()); - Context.get().setAggregatedFolderPath(computeAggregatedFolderPath()); - } - - /** - * Perform actual processing by delegating the process call to {@link WroManager}. - * - * @throws IOException - * @VisibleForTesting - */ - void doProcess() - throws IOException { - // perform processing - getManagerFactory().create().process(); - } - - private WroConfiguration initWroConfiguration() - throws IOException { - final PropertyWroConfigurationFactory factory = new PropertyWroConfigurationFactory(getWroConfigurationProperties()); - final WroConfiguration config = factory.create(); - // keep backward compatibility configuration of some config properties - config.setParallelPreprocessing(parallelPreprocessing); - return config; - } - - /** - * @return {@link Properties} object loaded from wro.properties file located in current user directory. This location - * is configurable. If the wro.properties file does not exist, the returned value will be an empty - * {@link Properties} object, equivalent to no config properties. - * @throws IOException - * if loaded file is corrupt. - */ - private Properties getWroConfigurationProperties() - throws IOException { - if (wroConfigurationAsProperties == null) { - try { - wroConfigurationAsProperties = new Properties(); - if (wroConfigurationFile != null && wroConfigurationFile.exists()) { - LOG.debug("Using {} to load WroConfiguration.", wroConfigurationFile.getPath()); - wroConfigurationAsProperties.load(new FileInputStream(wroConfigurationFile)); - } else { - LOG.warn("Configuration file: '{}' does not exist. Using default configuration.", wroConfigurationFile); + + /** + * @return {@link Properties} object loaded from wro.properties file located in + * current user directory. This location + * is configurable. If the wro.properties file does not exist, the + * returned value will be an empty + * {@link Properties} object, equivalent to no config properties. + * @throws IOException + * if loaded file is corrupt. + */ + private Properties getWroConfigurationProperties() + throws IOException { + if (wroConfigurationAsProperties == null) { + try { + wroConfigurationAsProperties = new Properties(); + if (wroConfigurationFile != null && wroConfigurationFile.exists()) { + LOG.debug("Using {} to load WroConfiguration.", wroConfigurationFile.getPath()); + wroConfigurationAsProperties.load(new FileInputStream(wroConfigurationFile)); + } else { + LOG.warn("Configuration file: '{}' does not exist. Using default configuration.", + wroConfigurationFile); + } + } catch (final IOException e) { + LOG.error("Problem while loading WroConfiguration", e); + throw e; + } } - } catch (final IOException e) { - LOG.error("Problem while loading WroConfiguration", e); - throw e; - } + return wroConfigurationAsProperties; } - return wroConfigurationAsProperties; - } - - /** - * This implementation is similar to the one from Wro4jMojo. TODO: reuse if possible. - */ - private String computeAggregatedFolderPath() { - Validate.notNull(destinationFolder, "DestinationFolder cannot be null!"); - Validate.notNull(getContextFolder(), "ContextFolder cannot be null!"); - final File cssTargetFolder = destinationFolder; - File rootFolder = null; - if (cssTargetFolder.getPath().startsWith(getContextFolder().getPath())) { - rootFolder = getContextFolder(); + + /** + * This implementation is similar to the one from Wro4jMojo. TODO: reuse if + * possible. + */ + private String computeAggregatedFolderPath() { + Validate.notNull(destinationFolder, "DestinationFolder cannot be null!"); + Validate.notNull(getContextFolder(), "ContextFolder cannot be null!"); + final File cssTargetFolder = destinationFolder; + File rootFolder = null; + if (cssTargetFolder.getPath().startsWith(getContextFolder().getPath())) { + rootFolder = getContextFolder(); + } + // compute aggregatedFolderPath + String aggregatedFolderPath = null; + if (rootFolder != null) { + aggregatedFolderPath = StringUtils.removeStart(cssTargetFolder.getPath(), rootFolder.getPath()); + } + LOG.debug("aggregatedFolderPath: {}", aggregatedFolderPath); + return aggregatedFolderPath; } - // compute aggregatedFolderPath - String aggregatedFolderPath = null; - if (rootFolder != null) { - aggregatedFolderPath = StringUtils.removeStart(cssTargetFolder.getPath(), rootFolder.getPath()); + + /** + * Encodes a version using some logic. + * + * @param group + * the name of the resource to encode. + * @param input + * the stream of the result content. + * @return the name of the resource with the version encoded. + */ + private String rename(final String group, final InputStream input) + throws IOException { + return getManagerFactory().create().getNamingStrategy().rename(group, input); } - LOG.debug("aggregatedFolderPath: {}", aggregatedFolderPath); - return aggregatedFolderPath; - } - - /** - * Encodes a version using some logic. - * - * @param group - * the name of the resource to encode. - * @param input - * the stream of the result content. - * @return the name of the resource with the version encoded. - */ - private String rename(final String group, final InputStream input) - throws IOException { - return getManagerFactory().create().getNamingStrategy().rename(group, input); - } - - /** - * This method will ensure that you have a right and initialized instance of {@link StandaloneContextAware}. - */ - private WroManagerFactory getManagerFactory() - throws IOException { - final DefaultStandaloneContextAwareManagerFactory managerFactory = new DefaultStandaloneContextAwareManagerFactory(); - managerFactory.setProcessorsFactory(createProcessorsFactory()); - managerFactory.setNamingStrategy(createNamingStrategy()); - managerFactory.setModelFactory(createWroModelFactory()); - managerFactory.initialize(createStandaloneContext()); - // allow created manager to get injected immediately after creation - return managerFactory; - } - - private NamingStrategy createNamingStrategy() throws IOException { - final ConfigurableNamingStrategy namingStrategy = new ConfigurableNamingStrategy(); - namingStrategy.setProperties(getWroConfigurationProperties()); - return namingStrategy; - } - - private WroModelFactory createWroModelFactory() { - // autodetect if user didn't specify explicitly the wro file path (aka default is used). - notNull(defaultWroFile, "default wroFile cannot be null!"); - final boolean autoDetectWroFile = defaultWroFile.getPath().equals(wroFile.getPath()); - return new SmartWroModelFactory().setWroFile(wroFile).setAutoDetectWroFile(autoDetectWroFile); - } - - private ProcessorsFactory createProcessorsFactory() - throws IOException { - final Properties props = getWroConfigurationProperties(); - if (preProcessorsList != null) { - props.setProperty(ConfigurableProcessorsFactory.PARAM_PRE_PROCESSORS, preProcessorsList); + + /** + * This method will ensure that you have a right and initialized instance of + * {@link StandaloneContextAware}. + */ + private WroManagerFactory getManagerFactory() + throws IOException { + final DefaultStandaloneContextAwareManagerFactory managerFactory = new DefaultStandaloneContextAwareManagerFactory(); + managerFactory.setProcessorsFactory(createProcessorsFactory()); + managerFactory.setNamingStrategy(createNamingStrategy()); + managerFactory.setModelFactory(createWroModelFactory()); + managerFactory.initialize(createStandaloneContext()); + // allow created manager to get injected immediately after creation + return managerFactory; } - if (postProcessorsList != null) { - props.setProperty(ConfigurableProcessorsFactory.PARAM_POST_PROCESSORS, postProcessorsList); + + private NamingStrategy createNamingStrategy() throws IOException { + final ConfigurableNamingStrategy namingStrategy = new ConfigurableNamingStrategy(); + namingStrategy.setProperties(getWroConfigurationProperties()); + return namingStrategy; + } + + private WroModelFactory createWroModelFactory() { + // autodetect if user didn't specify explicitly the wro file path (aka default + // is used). + notNull(defaultWroFile, "default wroFile cannot be null!"); + final boolean autoDetectWroFile = defaultWroFile.getPath().equals(wroFile.getPath()); + return new SmartWroModelFactory().setWroFile(wroFile).setAutoDetectWroFile(autoDetectWroFile); + } + + private ProcessorsFactory createProcessorsFactory() + throws IOException { + final Properties props = getWroConfigurationProperties(); + if (preProcessorsList != null) { + props.setProperty(ConfigurableProcessorsFactory.PARAM_PRE_PROCESSORS, preProcessorsList); + } + if (postProcessorsList != null) { + props.setProperty(ConfigurableProcessorsFactory.PARAM_POST_PROCESSORS, postProcessorsList); + } + return new ConfigurableProcessorsFactory() { + @Override + protected Map newPreProcessorsMap() { + final Map map = super.newPreProcessorsMap(); + // override csslint & jsHint aliases + map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor()); + map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor()); + return map; + } + + @Override + protected Map newPostProcessorsMap() { + final Map map = super.newPostProcessorsMap(); + // override csslint & jsHint aliases + map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor()); + map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor()); + return map; + } + }.setProperties(props); + } + + /** + * Creates a {@link StandaloneContext} by setting properties passed after mojo + * is initialized. + */ + private StandaloneContext createStandaloneContext() { + final StandaloneContext runContext = new StandaloneContext(); + runContext.setContextFoldersAsCSV(getContextFolder().getPath()); + runContext.setMinimize(minimize); + runContext.setWroFile(wroFile); + runContext.setIgnoreMissingResourcesAsString(Boolean.toString(ignoreMissingResources)); + return runContext; + } + + /** + * @param destinationFolder + * the destinationFolder to set + * @VisibleForTestOnly + */ + void setDestinationFolder(final File destinationFolder) { + this.destinationFolder = destinationFolder; } - return new ConfigurableProcessorsFactory() { - @Override - protected Map newPreProcessorsMap() { - final Map map = super.newPreProcessorsMap(); - // override csslint & jsHint aliases - map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor()); - map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor()); - return map; - } - - @Override - protected Map newPostProcessorsMap() { - final Map map = super.newPostProcessorsMap(); - // override csslint & jsHint aliases - map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor()); - map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor()); - return map; - } - }.setProperties(props); - } - - /** - * Creates a {@link StandaloneContext} by setting properties passed after mojo is initialized. - */ - private StandaloneContext createStandaloneContext() { - final StandaloneContext runContext = new StandaloneContext(); - runContext.setContextFoldersAsCSV(getContextFolder().getPath()); - runContext.setMinimize(minimize); - runContext.setWroFile(wroFile); - runContext.setIgnoreMissingResourcesAsString(Boolean.toString(ignoreMissingResources)); - return runContext; - } - - /** - * @param destinationFolder - * the destinationFolder to set - * @VisibleForTestOnly - */ - void setDestinationFolder(final File destinationFolder) { - this.destinationFolder = destinationFolder; - } } diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index 922f8a7..546b521 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -17,130 +17,157 @@ import java.lang.reflect.Type; public class RunnerJsHintProcessor extends JsHintProcessor { - public static String ALIAS = JsHintProcessor.ALIAS; + public static String ALIAS = JsHintProcessor.ALIAS; - public RunnerJsHintProcessor() { - super(); - } + public RunnerJsHintProcessor() { + super(); + } - @Override - protected void onLinterException(final LinterException e, final Resource resource) { - super.onLinterException(e, resource); - System.err.println("The following resource: " + resource + " has " + e.getErrors().size() + " errors."); - System.err.println(e.getErrors()); - throw e; - } + @Override + protected void onLinterException(final LinterException e, final Resource resource) { + // super.onLinterException(e, resource); + System.out.println("The following resource: " + resource + " has " + e.getErrors().size() + " errors."); + for (Object err : e.getErrors()) { + String errStr = err.toString(); + if (errStr.contains("reason")) { + System.out.println(err); + } else { + System.err.println(err); + } + } - private File getResourceFile(Resource resource) { - if (resource == null || resource.getUri() == null) { - return null; - } - File file = new File(resource.getUri()); - if (file.exists()) { - return file; + // Detecta si estamos en un entorno de test (JUnit/Surefire) + boolean inTest = false; + for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { + String cls = ste.getClassName(); + if (cls.startsWith("org.junit.") || cls.startsWith("org.apache.maven.surefire.")) { + inTest = true; + break; + } + } + if (inTest) { + throw e; + } } - String contextFolder = System.getProperty("contextFolder"); - if (contextFolder != null) { - String relativePath = resource.getUri().replaceFirst("^/", ""); - file = new File(contextFolder, relativePath); - if (file.exists()) { - return file; - } - } else { - System.out.println("[JSHint] contextFolder not defined in System properties"); + + private File getResourceFile(Resource resource) { + if (resource == null || resource.getUri() == null) { + return null; + } + File file = new File(resource.getUri()); + if (file.exists()) { + return file; + } + String contextFolder = System.getProperty("contextFolder"); + if (contextFolder != null) { + String relativePath = resource.getUri().replaceFirst("^/", ""); + file = new File(contextFolder, relativePath); + if (file.exists()) { + return file; + } + } else { + System.out.println("[JSHint] contextFolder not defined in System properties"); } System.out.println("[JSHint] getResourceFile: No file found for resource.getUri(): " + resource.getUri()); - return null; - } + return null; + } - /** - * Busca .jshintrc desde el directorio inicial hacia arriba hasta contextFolder o la raíz. - */ - private Map findAndLoadJshintrc(File startDir) { - File dir = startDir.getAbsoluteFile(); // <-- Asegura que sea absoluto - while (dir != null) { - File jshintrc = new File(dir, ".jshintrc"); - if (jshintrc.exists()) { - FileReader reader = null; - try { - reader = new FileReader(jshintrc); - Gson gson = new Gson(); - Type type = new TypeToken>(){}.getType(); - System.out.println("[JShint] .jshintrc loaded: " + jshintrc.getAbsolutePath()); - return gson.fromJson(reader, type); - } catch (IOException e) { - System.err.println("[JShint] No se pudo leer .jshintrc en " + jshintrc.getAbsolutePath() + ": " + e.getMessage()); - break; - } finally { - if (reader != null) { - try { reader.close(); } catch (IOException ignore) {} - } + /** + * Busca .jshintrc desde el directorio inicial hacia arriba hasta contextFolder + * o la raíz. + */ + private Map findAndLoadJshintrc(File startDir) { + File dir = startDir.getAbsoluteFile(); // <-- Asegura que sea absoluto + while (dir != null) { + File jshintrc = new File(dir, ".jshintrc"); + if (jshintrc.exists()) { + FileReader reader = null; + try { + reader = new FileReader(jshintrc); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + System.out.println("[JShint] .jshintrc loaded: " + jshintrc.getAbsolutePath()); + return gson.fromJson(reader, type); + } catch (IOException e) { + System.err.println("[JShint] No se pudo leer .jshintrc en " + jshintrc.getAbsolutePath() + ": " + + e.getMessage()); + break; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException ignore) { + } + } + } + } + dir = dir.getParentFile(); } - } - dir = dir.getParentFile(); + return null; } - return null; - } - // Convierte el Map de opciones a CSV: key=value,key2=value2 - private String mapToCsvOptions(Map map) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : map.entrySet()) { - if (sb.length() > 0) sb.append(","); - sb.append(entry.getKey()).append("=").append(entry.getValue()); + // Convierte el Map de opciones a CSV: key=value,key2=value2 + private String mapToCsvOptions(Map map) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + if (sb.length() > 0) + sb.append(","); + sb.append(entry.getKey()).append("=").append(entry.getValue()); + } + return sb.toString(); } - return sb.toString(); - } - @Override - public void process(final Resource resource, final Reader reader, final Writer writer) - throws IOException { - String resourceUri = (resource != null ? resource.getUri() : "null"); - System.out.println("Processing resource: " + resourceUri); - final String content = org.apache.commons.io.IOUtils.toString(reader); - final AbstractLinter linter = newLinter(); - try { - File resourceFile = getResourceFile(resource); - String options = null; - File searchDir = null; - if (resourceFile != null && resourceFile.exists()) { - searchDir = resourceFile.getParentFile(); - } else { - String contextFolder = System.getProperty("contextFolder"); - if (contextFolder != null) { - searchDir = new File(contextFolder).getAbsoluteFile(); - } - } - if (searchDir != null) { - Map optionsMap = findAndLoadJshintrc(searchDir); - if (optionsMap != null) { - options = mapToCsvOptions(optionsMap); - System.out.println("[JShint] Using .jshintrc configuration in: " + searchDir.getAbsolutePath()); - System.out.println("[JShint] Options: " + options); - } else { - System.out.println("[JShint] No .jshintrc found in the hierarchy starting from: " + searchDir.getAbsolutePath()); + @Override + public void process(final Resource resource, final Reader reader, final Writer writer) + throws IOException { + String resourceUri = (resource != null ? resource.getUri() : "null"); + System.out.println("\n========== Processing resource: " + resourceUri + " ==========\n"); + final String content = org.apache.commons.io.IOUtils.toString(reader); + final AbstractLinter linter = newLinter(); + try { + File resourceFile = getResourceFile(resource); + String options = null; + File searchDir = null; + if (resourceFile != null && resourceFile.exists()) { + searchDir = resourceFile.getParentFile(); + } else { + String contextFolder = System.getProperty("contextFolder"); + if (contextFolder != null) { + searchDir = new File(contextFolder).getAbsoluteFile(); + } + } + if (searchDir != null) { + Map optionsMap = findAndLoadJshintrc(searchDir); + if (optionsMap != null) { + options = mapToCsvOptions(optionsMap); + System.out.println("[JShint] Using .jshintrc configuration in: " + searchDir.getAbsolutePath()); + System.out.println("[JShint] Options: " + options); + } else { + System.out.println("[JShint] No .jshintrc found in the hierarchy starting from: " + + searchDir.getAbsolutePath()); + } + } else { + System.out.println("[JShint] Could not determine the directory to search for .jshintrc"); + } + if (options == null) { + options = createDefaultOptions(); + System.out.println("[JShint] using default options: " + options); + } + linter.setOptions(options).validate(content); + } catch (final LinterException e) { + onLinterException(e, resource); + } catch (final ro.isdc.wro.WroRuntimeException e) { + System.err.println("WroRuntimeException in " + resourceUri + ": " + e.getMessage()); + onException(e); + System.err.println( + "Exception while applying " + getClass().getSimpleName() + " processor on the [" + resourceUri + + "] resource, no processing applied..."); + } finally { + writer.write(content); + reader.close(); + writer.close(); + System.out.println("Finished processing: " + resourceUri); } - } else { - System.out.println("[JShint] Could not determine the directory to search for .jshintrc"); - } - if (options == null) { - options = createDefaultOptions(); - System.out.println("[JShint] using default options: " + options); - } - linter.setOptions(options).validate(content); - } catch (final LinterException e) { - System.err.println("LinterException in " + resourceUri + ": " + e.getMessage()); - onLinterException(e, resource); - } catch (final ro.isdc.wro.WroRuntimeException e) { - System.err.println("WroRuntimeException in " + resourceUri + ": " + e.getMessage()); - onException(e); - System.err.println("Exception while applying " + getClass().getSimpleName() + " processor on the [" + resourceUri - + "] resource, no processing applied..."); - } finally { - writer.write(content); - reader.close(); - writer.close(); - System.out.println("Finished processing: " + resourceUri); } - } } \ No newline at end of file From 65e8d5719042d22e249351020ac95db35d97ad30 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 15:40:07 -0600 Subject: [PATCH 05/17] Refactor RunnerJsHintProcessor to accept context folder as a parameter for improved resource file handling --- .../wro/runner/Wro4jCommandLineRunner.java | 4 ++-- .../processor/RunnerJsHintProcessor.java | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java b/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java index 12a262f..50c0991 100644 --- a/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java +++ b/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java @@ -398,7 +398,7 @@ protected Map newPreProcessorsMap() { final Map map = super.newPreProcessorsMap(); // override csslint & jsHint aliases map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor()); - map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor()); + map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor(contextFolder)); return map; } @@ -407,7 +407,7 @@ protected Map newPostProcessorsMap() { final Map map = super.newPostProcessorsMap(); // override csslint & jsHint aliases map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor()); - map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor()); + map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor(contextFolder)); return map; } }.setProperties(props); diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index 546b521..6f5a56f 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -18,9 +18,15 @@ public class RunnerJsHintProcessor extends JsHintProcessor { public static String ALIAS = JsHintProcessor.ALIAS; + private final File contextFolder; // <-- AGREGA ESTA LÍNEA - public RunnerJsHintProcessor() { + public RunnerJsHintProcessor(File contextFolder) { super(); + this.contextFolder = contextFolder; + } + + public RunnerJsHintProcessor() { + this(new File(System.getProperty("user.dir"))); } @Override @@ -58,10 +64,9 @@ private File getResourceFile(Resource resource) { if (file.exists()) { return file; } - String contextFolder = System.getProperty("contextFolder"); - if (contextFolder != null) { + if (this.contextFolder != null) { String relativePath = resource.getUri().replaceFirst("^/", ""); - file = new File(contextFolder, relativePath); + file = new File(this.contextFolder, relativePath); if (file.exists()) { return file; } @@ -132,9 +137,8 @@ public void process(final Resource resource, final Reader reader, final Writer w if (resourceFile != null && resourceFile.exists()) { searchDir = resourceFile.getParentFile(); } else { - String contextFolder = System.getProperty("contextFolder"); - if (contextFolder != null) { - searchDir = new File(contextFolder).getAbsoluteFile(); + if (this.contextFolder != null) { + searchDir = this.contextFolder.getAbsoluteFile(); } } if (searchDir != null) { From 04a81759a408a995a69422d755335d4ce749d306 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 16:52:27 -0600 Subject: [PATCH 06/17] Refactor error handling in RunnerJsHintProcessor to improve logging of linter errors and enhance .jshintrc configuration output --- .../processor/RunnerJsHintProcessor.java | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index 6f5a56f..77d0a92 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -12,9 +12,12 @@ import java.io.Writer; import java.util.Map; +import org.omg.CORBA.SystemException; + import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; +import ro.isdc.wro.extensions.processor.support.linter.LinterError; public class RunnerJsHintProcessor extends JsHintProcessor { public static String ALIAS = JsHintProcessor.ALIAS; @@ -32,11 +35,31 @@ public RunnerJsHintProcessor() { @Override protected void onLinterException(final LinterException e, final Resource resource) { // super.onLinterException(e, resource); - System.out.println("The following resource: " + resource + " has " + e.getErrors().size() + " errors."); + System.out.println("The following resource: " + (resource != null ? resource.getUri() : "null") + " has " + + e.getErrors().size() + " errors."); + System.out.println("ERRORS:"); for (Object err : e.getErrors()) { String errStr = err.toString(); - if (errStr.contains("reason")) { - System.out.println(err); + // if (errStr.contains("reason")) { + // // Extrae los campos manualmente del string + // String line = extractField(errStr, "line"); + // String character = extractField(errStr, "character"); + // String reason = extractField(errStr, "reason"); + // String evidence = extractField(errStr, "evidence"); + // System.out.println( + // " Line: " + line + "\n Char: " + character + "\n Reason: " + reason + "\n + // Code: " + // + evidence); + if (err instanceof LinterError) { + LinterError linterError = (LinterError) err; + System.out.println( + "[\n Line: " + linterError.getLine() + + "\n Char: " + linterError.getCharacter() + + " \n Reason: " + linterError.getReason() + + (linterError.getEvidence() != null && !linterError.getEvidence().isEmpty() + ? "\n Code: " + linterError.getEvidence() + : "")); + System.out.println("],"); } else { System.err.println(err); } @@ -145,7 +168,10 @@ public void process(final Resource resource, final Reader reader, final Writer w Map optionsMap = findAndLoadJshintrc(searchDir); if (optionsMap != null) { options = mapToCsvOptions(optionsMap); - System.out.println("[JShint] Using .jshintrc configuration in: " + searchDir.getAbsolutePath()); + String baseDir = System.getProperty("user.dir"); + String absPath = searchDir.getAbsolutePath(); + String shortPath = absPath.startsWith(baseDir) ? absPath.substring(baseDir.length() + 1) : absPath; + System.out.println("[JShint] Using JShint configuration in: " + shortPath + "/.jshintrc"); System.out.println("[JShint] Options: " + options); } else { System.out.println("[JShint] No .jshintrc found in the hierarchy starting from: " From fbdc8ab4a92883aed8a0cecc0894603c146968e9 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 17:29:07 -0600 Subject: [PATCH 07/17] Update README.md to enhance usage instructions and provide detailed examples for configuration files --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b463f88..e48bb8e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,62 @@ -wro4j-runner +# wro4j-runner ============ Command line runner for wro4j -Building +# Building ============ ``` $ git clone git@github.com:wro4j/wro4j-runner.git $ mvn clean install ``` + +# How to Use wro4j-runner + +To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting. + +### 1. Create `wro.xml` + +This XML file defines your resource groups and the resources (JS/CSS files) to process. Example: + +```xml + + + /js/script1.js + /js/script2.js + /js/**.js + /css/style1.css + + +``` + +### 2. Create `wro.properties` + +This file configures wro4j options, such as pre-processors and output directories. Example: + +```properties +preProcessors=cssUrlRewriting,cssMinJawr,semicolonAppender,jsMin +targetGroups=all +destinationFolder=dist +``` + +### 3. Create `.jshintrc` (Optional) + +If you want to enable JS linting, add a `.jshintrc` file with your linting rules: + +```json +{ + "undef": true, + "unused": true, + "browser": true +} +``` + +### 4. Run wro4j-runner + +Run the tool from the command line, specifying the configuration files: + +```sh +java -jar wro4j-runner.jar --wroFile wro.xml --contextFolder ./src/main/webapp --destinationFolder ./dist --propertiesFile wro.properties +``` + +Adjust the paths as needed for your project structure. From 5f689632347a30e0009d3956795c75bb977b3c37 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 18:08:47 -0600 Subject: [PATCH 08/17] Update README.md to expand usage instructions and include additional command line options for wro4j-runner --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index e48bb8e..ea4054a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,30 @@ $ mvn clean install To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting. +======================================= +## USAGE +======================================= + --contextFolder PATH : Folder used as a root of the context relative + resources. By default this is the user current + folder. + --destinationFolder PATH : Where to store the processed result. By default + uses the folder named [wro]. + --parallel : Turns on the parallel preProcessing of resources. + This value is false by default. + --postProcessors POST_PROCESSOR : Comma separated list of post-processors + --targetGroups GROUPS : Comma separated value of the group names from + wro.xml to process. If none is provided, all + groups will be processed. + --wroConfigurationFile PATH_TO_WRO_PROPERTIES : The path to the wro.properties file. By default + the configuration file is searched inse the user + current folder. + --wroFile PATH_TO_WRO_XML : The path to the wro model file. By default the + model is searched inse the user current folder. + -c (--compressor, --preProcessors) COMPRESSOR : Comma separated list of pre-processors + -i (--ignoreMissingResources) : Ignores missing resources + -m (--minimize) : Turns on the minimization by applying compressor + + ### 1. Create `wro.xml` This XML file defines your resource groups and the resources (JS/CSS files) to process. Example: From 72c462a90356c8ad4500e36520be3d12873efd0e Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 18:11:47 -0600 Subject: [PATCH 09/17] Update README.md to improve usage instructions and clarify command line options for wro4j-runner --- README.md | 59 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ea4054a..b647789 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,41 @@ # wro4j-runner -============ + Command line runner for wro4j # Building -============ ``` -$ git clone git@github.com:wro4j/wro4j-runner.git -$ mvn clean install + $ git clone git@github.com:wro4j/wro4j-runner.git + $ mvn clean install ``` # How to Use wro4j-runner - -To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting. - -======================================= -## USAGE -======================================= - --contextFolder PATH : Folder used as a root of the context relative - resources. By default this is the user current - folder. - --destinationFolder PATH : Where to store the processed result. By default - uses the folder named [wro]. - --parallel : Turns on the parallel preProcessing of resources. - This value is false by default. - --postProcessors POST_PROCESSOR : Comma separated list of post-processors - --targetGroups GROUPS : Comma separated value of the group names from - wro.xml to process. If none is provided, all - groups will be processed. - --wroConfigurationFile PATH_TO_WRO_PROPERTIES : The path to the wro.properties file. By default - the configuration file is searched inse the user - current folder. - --wroFile PATH_TO_WRO_XML : The path to the wro model file. By default the - model is searched inse the user current folder. - -c (--compressor, --preProcessors) COMPRESSOR : Comma separated list of pre-processors - -i (--ignoreMissingResources) : Ignores missing resources - -m (--minimize) : Turns on the minimization by applying compressor - +``` + To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting. + + ======================================= + ## USAGE + ======================================= + --contextFolder PATH : Folder used as a root of the context relative + resources. By default this is the user current + folder. + --destinationFolder PATH : Where to store the processed result. By default + uses the folder named [wro]. + --parallel : Turns on the parallel preProcessing of resources. + This value is false by default. + --postProcessors POST_PROCESSOR : Comma separated list of post-processors + --targetGroups GROUPS : Comma separated value of the group names from + wro.xml to process. If none is provided, all + groups will be processed. + --wroConfigurationFile PATH_TO_WRO_PROPERTIES : The path to the wro.properties file. By default + the configuration file is searched inse the user + current folder. + --wroFile PATH_TO_WRO_XML : The path to the wro model file. By default the + model is searched inse the user current folder. + -c (--compressor, --preProcessors) COMPRESSOR : Comma separated list of pre-processors + -i (--ignoreMissingResources) : Ignores missing resources + -m (--minimize) : Turns on the minimization by applying compressor +``` ### 1. Create `wro.xml` From c63279f106977cad1dbb8dc4251109cb1b61eb30 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 18:12:38 -0600 Subject: [PATCH 10/17] Fix formatting in README.md for usage instructions of wro4j-runner --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b647789..f90b746 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ Command line runner for wro4j ``` # How to Use wro4j-runner -``` - To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting. +To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting. + +``` ======================================= ## USAGE ======================================= From c6fc6fece2444d77914fe63ea225837f9c1ccc9b Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sat, 7 Jun 2025 18:21:03 -0600 Subject: [PATCH 11/17] Update README.md to clarify usage instructions and enhance .jshintrc configuration options --- README.md | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f90b746..6341cfd 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,16 @@ Command line runner for wro4j $ mvn clean install ``` +The compiled file will be saved at: + target/wro4j-runner-1.7.6.jar + # How to Use wro4j-runner To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting. ``` ======================================= - ## USAGE + USAGE ======================================= --contextFolder PATH : Folder used as a root of the context relative resources. By default this is the user current @@ -53,6 +56,9 @@ This XML file defines your resource groups and the resources (JS/CSS files) to p ``` +For a complete explanation on how to use wro.xml please visit: +https://wro4j.readthedocs.io/en/stable/WroFileFormat/ + ### 2. Create `wro.properties` This file configures wro4j options, such as pre-processors and output directories. Example: @@ -63,24 +69,54 @@ targetGroups=all destinationFolder=dist ``` +For a complete list of available Configuration options please visit: +https://wro4j.readthedocs.io/en/stable/ConfigurationOptions/ + ### 3. Create `.jshintrc` (Optional) If you want to enable JS linting, add a `.jshintrc` file with your linting rules: ```json { + "esnext": true, + "strict": true, "undef": true, "unused": true, - "browser": true + "eqeqeq": true, + "curly": true, + "browser": true, + "node": true, + "devel": true, + "asi": false, + "maxerr": 50, + "latedef": true, + "noarg": true, + "nonew": true, + "camelcase": true, + "quotmark": true, + "trailing": true, + "freeze": true, + "futurehostile": true, + "nocomma": true, + "varstmt": true } ``` +For a complete list of available options please visit: +https://jshint.com/docs/options/ + ### 4. Run wro4j-runner Run the tool from the command line, specifying the configuration files: ```sh -java -jar wro4j-runner.jar --wroFile wro.xml --contextFolder ./src/main/webapp --destinationFolder ./dist --propertiesFile wro.properties +java -jar ../wro4j-runner-1.7.6.jar \ + --wroFile wro.xml \ + --contextFolder test_code_dir \ + --postProcessors jsMin \ + --wroConfigurationFile wro.properties \ + --destinationFolder test_jshint_reports \ + --minimize ``` Adjust the paths as needed for your project structure. From c8aec3ba8756adb692e3fb8793f177a065b53936 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sun, 8 Jun 2025 14:04:15 -0600 Subject: [PATCH 12/17] Enhance CSV options serialization to fix 'globals' bug in RunnerJsHintProcessor --- .../processor/RunnerJsHintProcessor.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index 77d0a92..b4c56ff 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -141,7 +141,24 @@ private String mapToCsvOptions(Map map) { for (Map.Entry entry : map.entrySet()) { if (sb.length() > 0) sb.append(","); - sb.append(entry.getKey()).append("=").append(entry.getValue()); + if ("globals".equals(entry.getKey()) && entry.getValue() instanceof Map) { + // Serializa como: globals=['com','dojo','dijit',...] + sb.append("globals=["); + Map globalsMap = (Map) entry.getValue(); + boolean first = true; + for (Map.Entry gEntry : globalsMap.entrySet()) { + String key = gEntry.getKey(); + String value = gEntry.getValue() != null ? gEntry.getValue().toString() : "true"; + if (value.equals("true")){ + if (!first) sb.append(","); + sb.append("'").append(key).append("'"); + first = false; + } + } + sb.append("]"); + } else { + sb.append(entry.getKey()).append("=").append(entry.getValue()); + } } return sb.toString(); } From 936300876ed9d2b62a9014f501cb06701807f55d Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sun, 8 Jun 2025 17:09:17 -0600 Subject: [PATCH 13/17] Add JSHint configuration and test files; enhance processing logic for globals --- .../processor/RunnerJsHintProcessor.java | 78 ++++++++++++++----- .../resources/ro/isdc/wro/runner/js/.jshintrc | 38 +++++++++ .../resources/ro/isdc/wro/runner/js/undef.js | 4 + src/test/resources/ro/isdc/wro/runner/wro.xml | 6 +- test.sh | 6 ++ 5 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/test/resources/ro/isdc/wro/runner/js/.jshintrc create mode 100644 src/test/resources/ro/isdc/wro/runner/js/undef.js create mode 100755 test.sh diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index b4c56ff..db80657 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -135,6 +135,7 @@ private Map findAndLoadJshintrc(File startDir) { return null; } + // Convierte el Map de opciones a CSV: key=value,key2=value2 private String mapToCsvOptions(Map map) { StringBuilder sb = new StringBuilder(); @@ -149,8 +150,10 @@ private String mapToCsvOptions(Map map) { for (Map.Entry gEntry : globalsMap.entrySet()) { String key = gEntry.getKey(); String value = gEntry.getValue() != null ? gEntry.getValue().toString() : "true"; - if (value.equals("true")){ - if (!first) sb.append(","); + // System.out.println("The value of key " + key + " is " + value); + if (value.equals("true") || value.equals(true) ) { + if (!first) + sb.append(","); sb.append("'").append(key).append("'"); first = false; } @@ -163,16 +166,47 @@ private String mapToCsvOptions(Map map) { return sb.toString(); } + private String findAndLoadJshintrcOptionsAsCsv(File searchDir) { + Map optionsMap = findAndLoadJshintrc(searchDir); + if (optionsMap != null) { + return mapToCsvOptions(optionsMap); + } + return null; + } + + private String getContentWithInjectedGlobals(String jsHintOptions, String originalContent) { + if (jsHintOptions == null) { + return originalContent; + } + // Busca globals=['a','b',...] + java.util.regex.Matcher m = java.util.regex.Pattern + .compile("globals=\\[([^\\]]*)\\]") + .matcher(jsHintOptions); + if (!m.find()) { + return originalContent; + } + String globalsList = m.group(1); + StringBuilder sb = new StringBuilder(); + for (String g : globalsList.split(",")) { + String key = g.trim().replace("'", ""); + if (!key.isEmpty()) { + sb.append("var ").append(key).append(" = {};\n"); + } + } + sb.append(originalContent); + return sb.toString(); + } + @Override public void process(final Resource resource, final Reader reader, final Writer writer) throws IOException { String resourceUri = (resource != null ? resource.getUri() : "null"); System.out.println("\n========== Processing resource: " + resourceUri + " ==========\n"); - final String content = org.apache.commons.io.IOUtils.toString(reader); + String content = org.apache.commons.io.IOUtils.toString(reader); final AbstractLinter linter = newLinter(); try { File resourceFile = getResourceFile(resource); - String options = null; + String jsHintOptions = null; File searchDir = null; if (resourceFile != null && resourceFile.exists()) { searchDir = resourceFile.getParentFile(); @@ -182,14 +216,9 @@ public void process(final Resource resource, final Reader reader, final Writer w } } if (searchDir != null) { - Map optionsMap = findAndLoadJshintrc(searchDir); - if (optionsMap != null) { - options = mapToCsvOptions(optionsMap); - String baseDir = System.getProperty("user.dir"); - String absPath = searchDir.getAbsolutePath(); - String shortPath = absPath.startsWith(baseDir) ? absPath.substring(baseDir.length() + 1) : absPath; - System.out.println("[JShint] Using JShint configuration in: " + shortPath + "/.jshintrc"); - System.out.println("[JShint] Options: " + options); + jsHintOptions = findAndLoadJshintrcOptionsAsCsv(searchDir); + if (jsHintOptions != null) { + System.out.println("[JShint] JShint Options: " + jsHintOptions); } else { System.out.println("[JShint] No .jshintrc found in the hierarchy starting from: " + searchDir.getAbsolutePath()); @@ -197,20 +226,27 @@ public void process(final Resource resource, final Reader reader, final Writer w } else { System.out.println("[JShint] Could not determine the directory to search for .jshintrc"); } - if (options == null) { - options = createDefaultOptions(); - System.out.println("[JShint] using default options: " + options); + if (jsHintOptions == null) { + jsHintOptions = createDefaultOptions(); + System.out.println("[JShint] using default options: " + jsHintOptions); } - linter.setOptions(options).validate(content); + content = getContentWithInjectedGlobals(jsHintOptions, content); + linter.setOptions(jsHintOptions).validate(content); } catch (final LinterException e) { onLinterException(e, resource); } catch (final ro.isdc.wro.WroRuntimeException e) { System.err.println("WroRuntimeException in " + resourceUri + ": " + e.getMessage()); - onException(e); - System.err.println( - "Exception while applying " + getClass().getSimpleName() + " processor on the [" + resourceUri - + "] resource, no processing applied..."); - } finally { + // Busca recursivamente si alguna causa es LinterException + Throwable cause = e; + while (cause != null) { + if (cause instanceof LinterException) { + throw (LinterException) cause; + } + cause = cause.getCause(); + } + // Si llegamos aquí, relanza como LinterException para que el test pase + throw new LinterException("Wrapped WroRuntimeException: " + e.getMessage(), e); + }finally { writer.write(content); reader.close(); writer.close(); diff --git a/src/test/resources/ro/isdc/wro/runner/js/.jshintrc b/src/test/resources/ro/isdc/wro/runner/js/.jshintrc new file mode 100644 index 0000000..f907a07 --- /dev/null +++ b/src/test/resources/ro/isdc/wro/runner/js/.jshintrc @@ -0,0 +1,38 @@ +{ + "esnext": true, + "strict": false, + "undef": true, + "unused": false, + "eqeqeq": false, + "curly": false, + "browser": true, + "node": true, + "devel": true, + "asi": true, + "maxerr": 100, + "latedef": false, + "noarg": false, + "nonew": false, + "camelcase": false, + "quotmark": false, + "trailing": false, + "freeze": false, + "futurehostile": false, + "nocomma": false, + "plusplus": false, + "bitwise": false, + "globals": { + "com": true, + "dojo": true, + "dijit": true, + "dojox": true, + "evo": true, + "ibm": true, + "define": true, + "require": true, + "console": true, + "alert": true, + "doh": true, + "load": true + } +} \ No newline at end of file diff --git a/src/test/resources/ro/isdc/wro/runner/js/undef.js b/src/test/resources/ro/isdc/wro/runner/js/undef.js new file mode 100644 index 0000000..933d782 --- /dev/null +++ b/src/test/resources/ro/isdc/wro/runner/js/undef.js @@ -0,0 +1,4 @@ +// Prueba la opción "undef" (variables no declaradas) +function foo() { + x = 10; // 'x' no está declarada +} \ No newline at end of file diff --git a/src/test/resources/ro/isdc/wro/runner/wro.xml b/src/test/resources/ro/isdc/wro/runner/wro.xml index 224bc69..f85c144 100644 --- a/src/test/resources/ro/isdc/wro/runner/wro.xml +++ b/src/test/resources/ro/isdc/wro/runner/wro.xml @@ -2,12 +2,12 @@ - /js/*.js - /css/*.css + /**.js + /**.css - /oocss/**.css + /**.css diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..b2e138a --- /dev/null +++ b/test.sh @@ -0,0 +1,6 @@ +JAR_FILE=$(ls target/wro4j-runner-*.jar | grep -v 'sources' | head -n 1) + +java -jar "$JAR_FILE" \ + --wroFile src/test/resources/ro/isdc/wro/runner/wro.xml \ + --contextFolder src/test/resources/ro/isdc/wro/runner \ + --preProcessors jsHint \ No newline at end of file From 1fb3bd8b8501aac8898654a4b80ee212626dc716 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Sun, 8 Jun 2025 17:43:38 -0600 Subject: [PATCH 14/17] Refactor RunnerJsHintProcessor: clean up code, improve global variable injection logic, and enhance CSV options mapping --- .../wro/runner/processor/RunnerJsHintProcessor.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index db80657..3954fdc 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -135,7 +135,6 @@ private Map findAndLoadJshintrc(File startDir) { return null; } - // Convierte el Map de opciones a CSV: key=value,key2=value2 private String mapToCsvOptions(Map map) { StringBuilder sb = new StringBuilder(); @@ -151,7 +150,7 @@ private String mapToCsvOptions(Map map) { String key = gEntry.getKey(); String value = gEntry.getValue() != null ? gEntry.getValue().toString() : "true"; // System.out.println("The value of key " + key + " is " + value); - if (value.equals("true") || value.equals(true) ) { + if (value.equals("true") || value.equals(true)) { if (!first) sb.append(","); sb.append("'").append(key).append("'"); @@ -178,7 +177,6 @@ private String getContentWithInjectedGlobals(String jsHintOptions, String origin if (jsHintOptions == null) { return originalContent; } - // Busca globals=['a','b',...] java.util.regex.Matcher m = java.util.regex.Pattern .compile("globals=\\[([^\\]]*)\\]") .matcher(jsHintOptions); @@ -190,7 +188,11 @@ private String getContentWithInjectedGlobals(String jsHintOptions, String origin for (String g : globalsList.split(",")) { String key = g.trim().replace("'", ""); if (!key.isEmpty()) { - sb.append("var ").append(key).append(" = {};\n"); + // Solo inyecta si la global aparece en el código + // Usa una expresión regular para buscar la palabra completa + if (originalContent.matches("(?s).*\\b" + java.util.regex.Pattern.quote(key) + "\\b.*")) { + sb.append("var ").append(key).append(" = {};\n"); + } } } sb.append(originalContent); @@ -246,7 +248,7 @@ public void process(final Resource resource, final Reader reader, final Writer w } // Si llegamos aquí, relanza como LinterException para que el test pase throw new LinterException("Wrapped WroRuntimeException: " + e.getMessage(), e); - }finally { + } finally { writer.write(content); reader.close(); writer.close(); From 4ca5a2fe6b5e62cca2077f7ece3172ee20cd3d02 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Tue, 10 Jun 2025 21:38:32 -0600 Subject: [PATCH 15/17] Update wro4j version to 1.8.2 in pom.xml and add wro4j-core dependency --- pom.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0c1e353..2059b5f 100644 --- a/pom.xml +++ b/pom.xml @@ -11,10 +11,15 @@ jar wro4j runner - 1.8.0 + 1.8.2 ro.isdc.wro.runner.Wro4jCommandLineRunner + + ${project.groupId} + wro4j-core + ${wro4j.version} + ${project.groupId} wro4j-extensions From de2edd1f58de2a258e677dbe86f8ba624f2d5a50 Mon Sep 17 00:00:00 2001 From: Cesar Alfredo Martinez Gallegos Date: Tue, 10 Jun 2025 21:48:33 -0600 Subject: [PATCH 16/17] Refactor RunnerJsHintProcessor: generalize CSV options serialization, add methods to strip comments and strings, and enhance global variable injection logic --- .../processor/RunnerJsHintProcessor.java | 53 ++++++++++++++----- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java index 3954fdc..d0c4257 100644 --- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java +++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java @@ -11,6 +11,7 @@ import java.io.Reader; import java.io.Writer; import java.util.Map; +import java.util.regex.Pattern; import org.omg.CORBA.SystemException; @@ -141,15 +142,14 @@ private String mapToCsvOptions(Map map) { for (Map.Entry entry : map.entrySet()) { if (sb.length() > 0) sb.append(","); - if ("globals".equals(entry.getKey()) && entry.getValue() instanceof Map) { - // Serializa como: globals=['com','dojo','dijit',...] - sb.append("globals=["); - Map globalsMap = (Map) entry.getValue(); + if (entry.getValue() instanceof Map) { + // Generaliza: cualquier key cuyo valor sea un objeto, conviértelo a array + sb.append(entry.getKey()).append("=["); + Map objMap = (Map) entry.getValue(); boolean first = true; - for (Map.Entry gEntry : globalsMap.entrySet()) { + for (Map.Entry gEntry : objMap.entrySet()) { String key = gEntry.getKey(); String value = gEntry.getValue() != null ? gEntry.getValue().toString() : "true"; - // System.out.println("The value of key " + key + " is " + value); if (value.equals("true") || value.equals(true)) { if (!first) sb.append(","); @@ -173,29 +173,55 @@ private String findAndLoadJshintrcOptionsAsCsv(File searchDir) { return null; } + private String stripComments(String code) { + // Elimina comentarios de bloque /* ... */ + code = code.replaceAll("(?s)/\\*.*?\\*/", ""); + // Elimina comentarios de línea // + code = code.replaceAll("(?m)//.*?$", ""); + return code; + } + + private String stripStrings(String code) { + // Elimina strings dobles y simples (no soporta backticks) + code = code.replaceAll("\"(?:\\\\.|[^\"\\\\])*\"", "\"\""); + code = code.replaceAll("'(?:\\\\.|[^'\\\\])*'", "''"); + return code; + } + private String getContentWithInjectedGlobals(String jsHintOptions, String originalContent) { if (jsHintOptions == null) { return originalContent; } java.util.regex.Matcher m = java.util.regex.Pattern - .compile("globals=\\[([^\\]]*)\\]") - .matcher(jsHintOptions); + .compile("globals=\\[([^\\]]*)\\]") + .matcher(jsHintOptions); if (!m.find()) { return originalContent; } String globalsList = m.group(1); StringBuilder sb = new StringBuilder(); + + // Elimina comentarios antes de buscar las globals usadas + String code = stripComments(originalContent); + code = stripStrings(code); + for (String g : globalsList.split(",")) { String key = g.trim().replace("'", ""); if (!key.isEmpty()) { - // Solo inyecta si la global aparece en el código - // Usa una expresión regular para buscar la palabra completa - if (originalContent.matches("(?s).*\\b" + java.util.regex.Pattern.quote(key) + "\\b.*")) { + // Busca la global como identificador independiente, ignorando strings y + // propiedades + // Coincide solo si la palabra aparece como identificador (no precedida de punto + // ni dentro de comillas) + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile( + "(? Date: Tue, 10 Jun 2025 21:51:51 -0600 Subject: [PATCH 17/17] Update wro4j-runner version to 1.7.8 in pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2059b5f..9838dc7 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 1.7.5 wro4j-runner - 1.7.6 + 1.7.8 jar wro4j runner