diff --git a/lib/common/files.js b/lib/common/files.js
index 47ca039..61d20f8 100644
--- a/lib/common/files.js
+++ b/lib/common/files.js
@@ -57,7 +57,6 @@ function getRef(seed, type){
return seed[type].toString(36) + type;
}
-
/**
* @class File
*/
@@ -299,6 +298,9 @@ var FileManager = function(baseURI, relBaseURI, console, flow){
this.warns = [];
this.readInfo = [];
+
+ // helpers
+ this.abspath = abspath;
};
FileManager.prototype = {
diff --git a/lib/lint/command.js b/lib/lint/command.js
index 37ed6a1..27c9ecb 100644
--- a/lib/lint/command.js
+++ b/lib/lint/command.js
@@ -2,6 +2,7 @@ var path = require('path');
var clap = require.main.require('clap');
var common = require('../common/command');
var isChildProcess = typeof process.send == 'function'; // child process has send method
+var handleUnusedFiles = require('./reporter/parallel-process-unused-files');
function resolveCwd(value){
return path.resolve(process.env.PWD || process.cwd(), value);
@@ -20,6 +21,7 @@ module.exports = clap.create('lint', '[fileOrPreset]')
.option('--no-color', 'Suppress color output')
.option('--silent', 'No any output')
+ .option('--warn-unused-files
', 'Warn about unused files for specified path. Avoid using with --js-cut-dev since it might cause to incorrect results')
.option('--filter ', 'Show warnings only for specified file', resolveCwd)
.option('-r, --reporter ', 'Reporter console (default), checkstyle, junit',
function(reporter){
@@ -53,6 +55,8 @@ module.exports.getParallelOptions = function(){
return {
silent: true,
callback: function(res){
+ handleUnusedFiles(res);
+
var reporter = require(require('./reporter')[command.values.reporter]);
var data = require('./reporter/parallel-process-warns.js')(res);
console.log(reporter(data));
diff --git a/lib/lint/helpers/flow-file-collector.js b/lib/lint/helpers/flow-file-collector.js
new file mode 100644
index 0000000..8659381
--- /dev/null
+++ b/lib/lint/helpers/flow-file-collector.js
@@ -0,0 +1,21 @@
+module.exports = function collectFiles(flow, callback){
+ var stack = [flow.files.queue[0]];
+ var collectedFiles = {};
+ var handled = new WeakSet();
+ var cursor;
+
+ while (cursor = stack.pop())
+ {
+ // mark file as handled
+ handled.add(cursor);
+ callback(cursor);
+ cursor.linkTo.forEach(function(link){
+ // prevent handling files that are already handled
+ if (link[0] && !handled.has(link[0])) {
+ stack.push(link[0]);
+ }
+ });
+ }
+
+ return collectedFiles;
+};
diff --git a/lib/lint/helpers/fs-files-collector.js b/lib/lint/helpers/fs-files-collector.js
new file mode 100644
index 0000000..33a17e1
--- /dev/null
+++ b/lib/lint/helpers/fs-files-collector.js
@@ -0,0 +1,44 @@
+var path = require('path');
+var fs = require('fs');
+
+module.exports = function(base, extFilter){
+ var stack = [base];
+ var cursor;
+ var collectedFiles = {};
+
+ while (cursor = stack.pop())
+ {
+ if (!fs.existsSync(cursor))
+ continue;
+
+ var stat = fs.lstatSync(cursor);
+
+ if (stat.isSymbolicLink())
+ {
+ var resolvedLink = path.resolve(cursor, fs.readlinkSync(cursor));
+
+ stack.push(resolvedLink);
+ }
+ else if (stat.isDirectory())
+ {
+ var items = fs.readdirSync(cursor);
+
+ for (var i = 0; i < items.length; i++)
+ stack.push(path.join(cursor, items[i]));
+ }
+ else
+ {
+ if (extFilter)
+ {
+ var fileExt = path.extname(cursor);
+
+ if (fileExt.toLowerCase() === extFilter.toLowerCase())
+ collectedFiles[cursor] = true;
+ }
+ else
+ collectedFiles[cursor] = true;
+ }
+ }
+
+ return collectedFiles;
+};
diff --git a/lib/lint/index.js b/lib/lint/index.js
index 8a73339..a38423b 100644
--- a/lib/lint/index.js
+++ b/lib/lint/index.js
@@ -7,6 +7,7 @@ var extract = require('../extract');
var command = require('./command');
var chalk = require('chalk');
var isChildProcess = typeof process.send == 'function'; // child process has send method
+var unusedFiles = require('./unused/files');
if (isChildProcess)
process.on('uncaughtException', function(error){
@@ -91,6 +92,13 @@ function lint(config){
l10nInfo: true
}).concat([
function(flow){
+ if (options.warnUnusedFiles)
+ {
+ flow.usedFiles = unusedFiles.collectUsed(flow);
+ if (!isChildProcess)
+ unusedFiles.warn(flow);
+ }
+
flow.result = require('./reporter/process-warns')(flow.warns, options.filter);
}
]);
@@ -148,6 +156,7 @@ function lint(config){
event: 'done',
success: !flow.warns.length,
warnings: flow.warns,
+ usedFiles: flow.usedFiles,
result: flow.result
});
});
diff --git a/lib/lint/reporter/parallel-process-unused-files.js b/lib/lint/reporter/parallel-process-unused-files.js
new file mode 100644
index 0000000..1e6db89
--- /dev/null
+++ b/lib/lint/reporter/parallel-process-unused-files.js
@@ -0,0 +1,41 @@
+var path = require('path');
+var collectFiles = require('../helpers/fs-files-collector');
+
+module.exports = function handleUnusedFiles(tasks){
+ var basePaths = {};
+ var usedFiles = {};
+
+ // merge used files from tasks
+ tasks.forEach(function(task){
+ if (task.result.usedFiles)
+ {
+ basePaths[task.result.usedFiles.collectPath] = true;
+ task.result.usedFiles.items.forEach(function(filename){
+ usedFiles[task.result.usedFiles.basePath + filename] = true;
+ });
+ }
+ });
+
+ if (Object.keys(basePaths).length)
+ {
+ var task = { name: 'unused files', result: { warnings: [] } };
+
+ // collect unused files
+ for (var basePath in basePaths)
+ {
+ var files = collectFiles(basePath);
+
+ for (var fileName in files)
+ if (!usedFiles.hasOwnProperty(fileName))
+ {
+ // warm about unused file
+ fileName = path.relative(process.cwd(), fileName);
+ task.result.warnings.push({
+ file: fileName
+ });
+ }
+ }
+
+ tasks.push(task);
+ }
+};
diff --git a/lib/lint/reporter/parallel-process-warns.js b/lib/lint/reporter/parallel-process-warns.js
index 70f3309..66bdc89 100644
--- a/lib/lint/reporter/parallel-process-warns.js
+++ b/lib/lint/reporter/parallel-process-warns.js
@@ -17,7 +17,7 @@ module.exports = function(tasks){
failures.push({
loc: warn.loc,
- message: warn.message + ' at ' + filename
+ message: warn.message ? warn.message + ' at ' + filename : filename
});
});
});
diff --git a/lib/lint/unused/files.js b/lib/lint/unused/files.js
new file mode 100644
index 0000000..4f1e7ac
--- /dev/null
+++ b/lib/lint/unused/files.js
@@ -0,0 +1,77 @@
+var flowFilesCollector = require('../helpers/flow-file-collector');
+var collectFiles = require('../helpers/fs-files-collector');
+
+function isTarget(basePath, collectPath, file){
+ return file.filename && (basePath + file.filename).indexOf(collectPath + '/') === 0;
+}
+
+exports.collectUsed = function(flow){
+ var options = flow.options;
+ var basePath = options.base;
+ var collectPath = flow.files.abspath(basePath, options.warnUnusedFiles);
+ var usedFiles = {};
+
+ flowFilesCollector(flow, function(file){
+ if (isTarget(basePath, collectPath, file))
+ usedFiles[file.filename] = true;
+
+ if (file.type == 'template')
+ {
+ if (file.decl.deps)
+ {
+ file.decl.deps.forEach(function(resource){
+ if (!resource.virtual && isTarget(basePath, collectPath, { filename: resource.url }))
+ usedFiles[resource.url] = true;
+ });
+ }
+ if (file.decl.l10n)
+ {
+ file.decl.l10n.forEach(function(item){
+ var l10nInfo = item.split('@');
+ var dictFilename = l10nInfo[1];
+
+ if (isTarget(basePath, collectPath, { filename: dictFilename }))
+ usedFiles[dictFilename] = true;
+ });
+ }
+ if (file.decl.styles)
+ {
+ file.decl.styles.forEach(function(style){
+ if (!style.resource && isTarget(basePath, collectPath, { filename: style.sourceUrl }))
+ usedFiles[style.sourceUrl] = true;
+ });
+ }
+ }
+ });
+
+ return {
+ basePath: basePath,
+ collectPath: collectPath,
+ items: Object.keys(usedFiles)
+ };
+};
+
+exports.warn = function(flow){
+ if (flow.options.warnUnusedFiles && flow.usedFiles)
+ {
+ var usedFilesInfo = flow.usedFiles;
+ var usedFiles = {};
+ var basePath = usedFilesInfo.collectPath;
+ var collectedFiles = collectFiles(basePath);
+
+ usedFilesInfo.items.forEach(function(filename){
+ usedFiles[usedFilesInfo.basePath + filename] = true;
+ });
+
+ for (var usedFile in usedFiles)
+ delete collectedFiles[usedFile];
+
+ for (var unusedName in collectedFiles) {
+ unusedName = unusedName.slice(process.cwd().length);
+ flow.warn({
+ file: 'unused files',
+ message: unusedName
+ });
+ }
+ }
+};