Skip to content

Commit

Permalink
Implement follow include directives
Browse files Browse the repository at this point in the history
  • Loading branch information
jhinch committed Mar 23, 2019
1 parent d54937e commit 7e72bcb
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 104 deletions.
83 changes: 50 additions & 33 deletions bin/_cli/commands.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
let fs = require('fs');
let parser = require('../../lib/parser');
let {runRules} = require('../../lib/validator');
let builtinRules = require('../../lib/rules').builtins;
let optionsParser = require('./options');
let {table, getBorderCharacters} = require('table');
let chalk = require('chalk');
let glob = require('glob');
let path = require('path');

const TABLE_CONFIG = {
border: getBorderCharacters('void'),
Expand Down Expand Up @@ -70,51 +69,69 @@ function help(options, output) {
}

function validate(options, output) {
let files = findFiles(options.includes, options.excludes);
let errorCount = files.map(file => {
let fileContents = fs.readFileSync(file, 'utf8');
let parseTree = parser.parse(fileContents);
let results = runValidationWithBuiltins(parseTree);
let fileNodes = parser.parseFiles({
includes: options.includes,
excludes: options.excludes,
maxDepth: options.followIncludes ? options.maxIncludeDepth : 0,
});
let errorCount = fileNodes.map(fileNode => {
let results = runValidationWithBuiltins(fileNode);
if (results.length) {
outputResults(file, results, output);
outputResults(fileNode.name, results, output);
}
return results.filter(result => result.type === 'error').length;
return countErrors(results);
}).reduce((a, b) => a + b, 0);
outputSummary({ fileCount: files.length, errorCount }, output);
outputSummary({ fileCount: fileNodes.length, errorCount }, output);
return errorCount ? 1 : 0;
}

function runValidationWithBuiltins(parseTree) {
return runRules(parseTree, builtinRules);
}

function findFiles(includes, excludes) {
let includedFiles = findMatchingFiles(includes);
let excludedFiles = findMatchingFiles(excludes);
return includedFiles.filter(f => excludedFiles.indexOf(f) === -1);
function countErrors(results) {
return results.reduce((total, result) => {
if (result.type === 'error') {
return total + 1;
} else if (result.nested) {
return total + countErrors(result.nested.results);
} else {
return total;
}
}, 0);
}

function findMatchingFiles(globs) {
let files = [];
globs.forEach(globString => glob.sync(globString).forEach(file => files.push(file)));
return files;
function runValidationWithBuiltins(parseTree) {
return runRules(parseTree, builtinRules);
}

function outputResults(fileName, results, output) {
output.log('');
output.log('', results);
output.log(chalk.underline(fileName));
outputInnerResults(' ', fileName, results, output);
}

function outputInnerResults(indent, fileName, results, output) {
let tableData = [];
results.forEach(({pos, type, text, rule}) => {
tableData.push([
chalk.dim(pos.start.line),
chalk.dim(':'),
chalk.dim(pos.start.column),
chalk.red(type),
text,
chalk.dim(rule),
]);
let nestedResults = [];
results.forEach(({pos, type, text, nested, rule}) => {
if (nested) {
nestedResults.push(nested);
} else {
tableData.push([
chalk.dim(pos.start.line),
chalk.dim(':'),
chalk.dim(pos.start.column),
chalk.red(type),
text,
chalk.dim(rule),
]);
}
});
if (tableData.length) {
output.log(table(tableData, TABLE_CONFIG));
}
nestedResults.forEach(nested => {
let includeFileName = path.relative(path.dirname(path.resolve(fileName)), nested.fileName);
output.log(chalk.bold(indent + 'within'), chalk.underline(includeFileName));
outputInnerResults(indent + ' ', nested.fileName, nested.results, output);
});
output.log(table(tableData, TABLE_CONFIG));
}

function outputSummary({fileCount, errorCount}, output) {
Expand Down
1 change: 1 addition & 0 deletions bin/_cli/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const defaults = {
config: '~/.nginx-linter.json',
includes: ['/etc/nginx/*.conf', '/etc/nginx/**/*.conf'],
followIncludes: true,
maxIncludeDepth: 5,
excludes: [],
};

Expand Down
21 changes: 21 additions & 0 deletions lib/parser/include-visitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
let {walk} = require('../walker');

function visitIncludes(node, callback) {
walk(node, (node, state) => {
if (node.type === 'directive' && node.name === 'include') {
state = {node: node, includeGlob: null};
} else if (node.type === 'parameter' && state && state.includeGlob == null) {
state.includeGlob = node.text;
} else if (node.type !== 'whitespace' && node.type !== 'comment') {
if (node.type === 'punctuation' && node.text === ';' && state && state.includeGlob != null) {
callback(state.node, state.includeGlob);
}
state = null;
}
return state;
}, null);
}

module.exports = {
visitIncludes,
};
63 changes: 62 additions & 1 deletion lib/parser/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,64 @@
let parser = require('./parser');
let {visitIncludes} = require('./include-visitor');
let fs = require('fs');
let glob = require('glob');
let path = require('path');

module.exports = parser;
function parse(contents) {
return parser.parse(contents);
}

function parseFile(name) {
let contents = fs.readFileSync(name, 'utf8');
let root = parse(contents);
let node = {
type: 'file',
name,
root,
};
return node;
}

function parseFiles({includes, excludes, maxDepth}) {
includes = includes || [];
excludes = excludes || [];
maxDepth = maxDepth || 0;
let files = findFiles(includes, excludes);
let nodes = files.map(parseFile);
if (maxDepth > 0) {
nodes.forEach(node => {
let file = node.name;
let effects = [];
visitIncludes(node, (node, includeGlob) => {
includeGlob = path.resolve(path.dirname(file), includeGlob);
effects.push(() => {
node.includedFiles = parseFiles({
includes: [includeGlob],
excludes: excludes,
maxDepth: maxDepth - 1,
});
});
});
effects.forEach(effect => effect());
});
}
return nodes;
}

function findFiles(includes, excludes) {
let includedFiles = findMatchingFiles(includes);
let excludedFiles = findMatchingFiles(excludes);
return includedFiles.filter(f => excludedFiles.indexOf(f) === -1);
}

function findMatchingFiles(globs) {
let files = [];
globs.forEach(globString => glob.sync(globString).forEach(file => files.push(file)));
return files;
}

module.exports = {
parse,
parseFile,
parseFiles,
};
134 changes: 75 additions & 59 deletions lib/rules/rule-indentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,66 +59,82 @@ function resetIndentation(state) {
state.whitespace = '';
}

function invoke(node, errors, indentation, stack) {
stack = stack || [initialState()];
let state = stack[0];
switch (node.type) {
case 'whitespace':
trackIndentation(state, node.text);
break;
case 'newline':
resetIndentation(state);
break;
case 'comment':
// fall through
case 'directive':
validateIndentation(errors, indentation, state);
break;
case 'punctuation':
switch (node.text) {
case '{':
validateIndentation(errors, indentation, state);
increaseLevel(state);
break;
case '}':
decreaseLevel(state);
validateIndentation(errors, indentation, state);
break;
}
break;
case 'lua:code':
switch (node.text) {
case 'function':
// fall through
case 'do':
// fall through
case 'then':
// fall through
case 'repeat':
validateIndentation(errors, indentation, state);
increaseLevel(state);
break;
case 'else':
decreaseLevel(state);
validateIndentation(errors, indentation, state);
increaseLevel(state);
break;
case 'until':
// fall through
case 'end':
// fall through
case 'elseif':
decreaseLevel(state);
validateIndentation(errors, indentation, state);
break;
default:
validateIndentation(errors, indentation, state);
break;
}
break;
}
return stack;
}

function onFileStart(stack) {
stack = stack || [];
stack.unshift(initialState());
return stack;
}

function onFileEnd(stack) {
stack.shift();
return stack;
}

module.exports = {
name: 'indentation',
default: 4,
invoke: function(node, errors, indentation, state) {
state = state || initialState();
switch (node.type) {
case 'whitespace':
trackIndentation(state, node.text);
break;
case 'newline':
resetIndentation(state);
break;
case 'comment':
// fall through
case 'directive':
validateIndentation(errors, indentation, state);
break;
case 'punctuation':
switch (node.text) {
case '{':
validateIndentation(errors, indentation, state);
increaseLevel(state);
break;
case '}':
decreaseLevel(state);
validateIndentation(errors, indentation, state);
break;
}
break;
case 'lua:code':
switch (node.text) {
case 'function':
// fall through
case 'do':
// fall through
case 'then':
// fall through
case 'repeat':
validateIndentation(errors, indentation, state);
increaseLevel(state);
break;
case 'else':
decreaseLevel(state);
validateIndentation(errors, indentation, state);
increaseLevel(state);
break;
case 'until':
// fall through
case 'end':
// fall through
case 'elseif':
decreaseLevel(state);
validateIndentation(errors, indentation, state);
break;
default:
validateIndentation(errors, indentation, state);
break;
}
break;
}
return state;
},
invoke,
onFileStart,
onFileEnd,
};

0 comments on commit 7e72bcb

Please sign in to comment.