diff --git a/lib/init.coffee b/lib/init.coffee index 9311f36..7acd122 100755 --- a/lib/init.coffee +++ b/lib/init.coffee @@ -6,8 +6,6 @@ helpers = null voucher = null fs = null -cpConfigFileName = '.classpath' - module.exports = activate: (state) -> # state-object as preparation for user-notifications @@ -53,6 +51,11 @@ module.exports = (newValue) => @classpathFilename = newValue.trim() ) + @subscriptions.add( + atom.config.observe 'linter-javac.sourcepathFilename', + (newValue) => + @sourcepathFilename = newValue.trim() + ) @subscriptions.add( atom.config.observe 'linter-javac.javacArgsFilename', (newValue) => @@ -78,131 +81,80 @@ module.exports = helpers = require 'atom-linter' voucher = require 'voucher' fs = require 'fs' - if @verboseLogging - @_log 'requiring modules finished.' + @_log 'requiring modules finished.' - if @verboseLogging - @_log 'providing linter, examining javac-callability.' + @_log 'providing linter, examining javac-callability.' grammarScopes: ['source.java'] scope: 'project' lintOnFly: false # Only lint on save + lint: (textEditor) => filePath = textEditor.getPath() wd = path.dirname filePath searchDir = @getProjectRootDir() || path.dirname filePath + # Classpath cp = '' - if @verboseLogging - @_log 'starting to lint.' + # Sourcepath - this is a fair default for many Java projects + sp = 'src/main/java/' + + @_log 'starting to lint.' # Find project config file if it exists. - cpConfig = @findClasspathConfig(wd) + cpConfig = @findConfigFile(wd, @classpathFilename) if cpConfig? # Use the location of the config file as the working directory - wd = cpConfig.cfgDir + wd = cpConfig.dir # Use configured classpath - cp = cpConfig.cfgCp + cp = cpConfig.content # Use config file location to import correct files searchDir = wd + spConfig = @findConfigFile(wd, @sourcepathFilename) + if spConfig? + sp = spConfig.content + # Add extra classpath if provided cp += path.delimiter + @classpath if @classpath # Add environment variable if it exists cp += path.delimiter + process.env.CLASSPATH if process.env.CLASSPATH - if @verboseLogging - @_log 'start searching java-files with "', - searchDir, - '" as search-directory.' + @_log 'start searching java-files with "', searchDir, '" as search-directory.' lstats = fs.lstatSync searchDir - atom.project.repositoryForDirectory( - new Directory(searchDir, lstats.isSymbolicLink()) - ) - .then (repo) => - @getFilesEndingWith searchDir, - '.java', repo?.isPathIgnored.bind(repo) - .then (files) => - # Arguments to javac - args = ['-Xlint:all'] - args = args.concat(['-cp', cp]) if cp - - # add additional options to the args-array - if @additionalOptions.length > 0 - args = args.concat @additionalOptions - if @verboseLogging - @_log 'adding', - @additionalOptions.length, - 'additional javac-options.' - - if @verboseLogging - @_log 'collected the following arguments:', args.join(' ') - - # add javac argsfile if filename has been configured - if @javacArgsFilename - args.push('@' + @javacArgsFilename) - if @verboseLogging - @_log 'adding', @javacArgsFilename, 'as argsfile.' - - args.push.apply(args, files) - if @verboseLogging - @_log 'adding', - files.length, - 'files to the javac-arguments (from "', - files[0], - '" to "', - files[files.length - 1] - '").' - - # TODO: remove this quick fix - # count the size of expected execution-command - # see issue #58 for further details - cliLimit = if _os.platform() == 'win32' then 7900 else 130000 - expectedCmdSize = @javaExecutablePath.length - sliceIndex = 0 - for arg in args - expectedCmdSize++ # add prepending space - if (typeof arg) == 'string' - expectedCmdSize += arg.length - else - expectedCmdSize += arg.toString().length - if expectedCmdSize < cliLimit - sliceIndex++ - - if sliceIndex < (args.length - 1) - # coffeelint: disable=max_line_length - console.warn """ - linter-javac: The lint-command is presumed to break the limit of #{cliLimit} characters on the #{_os.platform()}-platform. - Dropping #{args.length - sliceIndex} source files, as a result javac may not resolve all dependencies. - """ - # coffeelint: enable=max_line_length - args = args.slice(0, sliceIndex) # cut args down - args.push(filePath) # ensure actual file is part - - - if @verboseLogging - @_log 'calling javac with', - args.length, - 'arguments by invoking "', @javaExecutablePath, - '". The approximated command length is', - args.join(' ').length, - 'characters long, the last argument is:', - args[args.length - 1] - - # Execute javac - helpers.exec(@javaExecutablePath, args, { - stream: 'stderr', - cwd: wd, - allowEmptyStderr: true - }) - .then (val) => - if @verboseLogging - @_log 'parsing:\n', val - @parse(val, textEditor) + # Arguments to javac + args = ['-Xlint:all'] + args = args.concat(['-cp', cp]) if cp + args = args.concat(['-sourcepath', sp]) if sp + + # add additional options to the args-array + if @additionalOptions.length > 0 + args = args.concat @additionalOptions + @_log 'adding', @additionalOptions.length, 'additional javac-options.' + + @_log 'collected the following arguments:', args.join(' ') + + # add javac argsfile if filename has been configured + if @javacArgsFilename + args.push('@' + @javacArgsFilename) + @_log 'adding', @javacArgsFilename, 'as argsfile.' + + # Append the file to actually lint + args.push(filePath) + + # Execute javac + helpers.exec(@javaExecutablePath, args, { + stream: 'stderr', + cwd: wd, + allowEmptyStderr: true + }) + .then (val) => + @_log 'parsing:\n', val + @parse(val, textEditor) parse: (javacOutput, textEditor) -> languageCode = @_detectLanguageCode javacOutput @@ -231,8 +183,7 @@ module.exports = lastIndex = messages.length - 1 messages[lastIndex].range[0][1] = column messages[lastIndex].range[1][1] = column + 1 - if @verboseLogging - @_log 'returning', messages.length, 'linter-messages.' + @_log 'returning', messages.length, 'linter-messages.' return messages @@ -253,59 +204,32 @@ module.exports = # TODO: The following fails if there's a symlink in the path return textEditor.getPath().substr(0, realpath.length) == realpath - getFilesEndingWith: (startPath, endsWith, ignoreFn) -> - foundFiles = [] - folderFiles = [] - voucher fs.readdir, startPath - .then (files) -> - folderFiles = files - Promise.all files.map (f) -> - filename = path.join startPath, f - voucher fs.lstat, filename - .then (fileStats) => - mapped = fileStats.map (stats, i) => - filename = path.join startPath, folderFiles[i] - if ignoreFn?(filename) - return undefined - else if stats.isDirectory() - return @getFilesEndingWith filename, endsWith, ignoreFn - else if filename.endsWith(endsWith) - return [ filename ] - - Promise.all(mapped.filter(Boolean)) - - .then (fileArrays) -> - [].concat.apply([], fileArrays) - - findClasspathConfig: (d) -> - # Search for the .classpath file starting in the given directory + findConfigFile: (directory, filename) -> + # Search for the config file starting in the given directory # and searching parent directories until it is found, or we go outside the # project base directory. - while atom.project.contains(d) or (d in atom.project.getPaths()) + while atom.project.contains(directory) or (directory in atom.project.getPaths()) try - file = path.join d, @classpathFilename + file = path.join directory, filename result = - cfgCp: fs.readFileSync(file, { encoding: 'utf-8' }) - cfgDir: d - result.cfgCp = result.cfgCp.trim() + content: fs.readFileSync(file, { encoding: 'utf-8' }).trim() + dir: directory return result catch e - d = path.dirname(d) + directory = path.dirname(directory) return null _detectLanguageCode: (javacOutput) -> - if @verboseLogging - @_log 'detecting languages' + @_log 'detecting languages' for language, pattern of @patterns if javacOutput.match(pattern.detector) - if @verboseLogging - @_log 'detected the following language-code:', language + @_log 'detected the following language-code:', language return language return false _log: (msgs...) -> - if (msgs.length > 0) + if (@verboseLogging && msgs.length > 0) javacPrefix = 'linter-javac: ' console.log javacPrefix + msgs.join(' ') diff --git a/package.json b/package.json index 4247cee..f41443d 100644 --- a/package.json +++ b/package.json @@ -27,32 +27,44 @@ "javacExecutablePath": { "type": "string", "default": "javac", - "description": "Path to the javac executable. This setting will be used to call the java-compiler. The entered value should be immediately callable on commandline. Example: `C:\\Program Files\\Java\\jdk1.6.0_16\\bin\\javac.exe`. Keep in mind that placeholders like `~` do **not** work. If your [path-variable](https://en.wikipedia.org/wiki/PATH_%28variable%29) is set properly it should not be necessary to change the default." + "description": "Path to the javac executable. This setting will be used to call the java-compiler. The entered value should be immediately callable on commandline. Example: `C:\\Program Files\\Java\\jdk1.6.0_16\\bin\\javac.exe`. Keep in mind that placeholders like `~` do **not** work. If your [path-variable](https://en.wikipedia.org/wiki/PATH_%28variable%29) is set properly it should not be necessary to change the default.", + "order": 1 }, "additionalClasspaths": { "type": "string", "default": "", - "description": "Additional classpaths to be used (for the `-cp`-option) when calling javac, separate multiple paths using the right path-delimiter for your os (`:`/`;`). Be aware that existing classpath-definitions from the environment variable \"CLASSPATH\" will be merged into the argument, as well as the content of your optional [`.classpath`-files](https://atom.io/packages/linter-javac). Example: `/path1:/path2` will become `javac -cp :/path1:/path2`. Keep in mind that placeholders like `~` do **not** work." + "description": "Additional classpaths to be used (for the `-cp`-option) when calling javac, separate multiple paths using the right path-delimiter for your os (`:`/`;`). Be aware that existing classpath-definitions from the environment variable \"CLASSPATH\" will be merged into the argument, as well as the content of your optional [`.classpath`-files](https://atom.io/packages/linter-javac). Example: `/path1:/path2` will become `javac -cp :/path1:/path2`. Keep in mind that placeholders like `~` do **not** work.", + "order": 2 }, "additionalJavacOptions": { "type": "string", "default": "", - "description": "Your additional options will be inserted between the javac-command and the sourcefiles. Example: `-d /root/class-cache` will become `javac -Xlint:all -d /root/class-cache .../Test.java` take a look to the [javac-docs](http://docs.oracle.com/javase/8/docs/technotes/tools/unix/javac.html) for further information on valid options. Keep in mind that placeholders like `~` do **not** work." + "description": "Your additional options will be inserted between the javac-command and the sourcefiles. Example: `-d /root/class-cache` will become `javac -Xlint:all -d /root/class-cache .../Test.java` take a look to the [javac-docs](http://docs.oracle.com/javase/8/docs/technotes/tools/unix/javac.html) for further information on valid options. Keep in mind that placeholders like `~` do **not** work.", + "order": 3 }, "classpathFilename": { "type": "string", "default": ".classpath", - "description": "You can change the default .classpath filename. This is a useful option if You e.g. bump into conflicts with Eclipse users." + "description": "You can change the default .classpath filename. This is a useful option if You e.g. bump into conflicts with Eclipse users.", + "order": 4 + }, + "sourcepathFilename": { + "type": "string", + "default": ".sourcepath", + "description": "This file should point out all the locations where you have sources. That way javac can find other source files you're referring to in your code (which are likely not on the classpath).", + "order": 5 }, "javacArgsFilename": { "type": "string", "default": "", - "description": "Optionally you can define filename for a [javac argsfile](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html#BHCCFGCD) that is located alongside with the .classpath file in the same directory. Contents of the argfile are passed to javac as arguments." + "description": "Optionally you can define filename for a [javac argsfile](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html#BHCCFGCD) that is located alongside with the .classpath file in the same directory. Contents of the argfile are passed to javac as arguments.", + "order": 6 }, "verboseLogging": { "type": "boolean", "default": false, - "description": "If enabled linter-javac will log its internal states to the console. This option is intended for debugging purposes **only** - enable this **only temporarily**, if the linter is not working as expected. You can access the output from the dev-console (alt+cmd+i), you may file the log-messages to a proper issue in order to help us improving this package. Do not forget to disable this option afterwards - since this may lead to performance issues." + "description": "If enabled linter-javac will log its internal states to the console. This option is intended for debugging purposes **only** - enable this **only temporarily**, if the linter is not working as expected. You can access the output from the dev-console (alt+cmd+i), you may file the log-messages to a proper issue in order to help us improving this package. Do not forget to disable this option afterwards - since this may lead to performance issues.", + "order": 7 } } }