diff --git a/docs/hub/.schema/linterhub.output.md b/docs/hub/.schema/linterhub.output.md new file mode 100644 index 0000000..342e014 --- /dev/null +++ b/docs/hub/.schema/linterhub.output.md @@ -0,0 +1,34 @@ +# Linterhub Output +This document describes the structure of linterhub output +## Structure +Linterhub output is an array of engines results +### result +The engine result + +|Key|Type|Required|Description| +|-|:-:|:-:|-| +|engine|string|+|The engine name that performed analysis| +|result|object|-|The analysis result| +|error|[error](#error)|-|The problem definition if analysis is not possible| +### error +The problem definition if analysis is not possible + +|Key|Type|Required|Description| +|-|:-:|:-:|-| +|code|integer|+|The error code| +|title|string|+|The error title| +|description|string|-|The error decription| +## Example +``` +[ + { + "engine": "string", + "result": {}, + "error": { + "code": 0, + "title": "string", + "description": "string" + } + } +] +``` \ No newline at end of file diff --git a/script/js/cs.js b/script/js/cs.js index 86e1e2c..77c63bf 100644 --- a/script/js/cs.js +++ b/script/js/cs.js @@ -11,7 +11,10 @@ Object.resolve = (path, obj) => path.replace('#/', '').split('/').reduce((prev, const format = { undef: (name) => name.replace('#/definitions/', ''), - class: (name) => `public class ${format.highCase(format.undef(name))}`, + class: (name, type) => { + let gname = format.highCase(format.undef(name)); + return `public class ${gname}` + (type ? ` : List<${gname}.${type}>` : ''); + }, highCase: (value) => value.charAt(0).toUpperCase() + value.slice(1), enum: (name, value) => { name = name.charAt(0).toUpperCase() + name.slice(1); @@ -40,6 +43,9 @@ const format = { if (propname === 'options' && name === 'linterhub.config.json') { type = 'EngineOptions'; } + if (propname === 'result') { + type = 'EngineOutputSchema'; + } if (type === 'int' || type === 'bool') { type += '?'; } @@ -162,7 +168,8 @@ const tree = { typeName = format.removeExtension(name + 'Schema'); } tree.doc.push(format.documentation(node.description, true)); - tree.doc.push(format.class(typeName)); + let elemType = node.type == 'array' ? format.type(node.items.$ref) : false; + tree.doc.push(format.class(typeName, elemType)); tree.doc.push(format.open()); if (node.properties) { Object.keys(node.properties).forEach((name) => { diff --git a/script/js/generate.json b/script/js/generate.json index 2b91750..652be35 100644 --- a/script/js/generate.json +++ b/script/js/generate.json @@ -29,6 +29,11 @@ "cs": "src/core/Schema/EngineSchema.cs", "md": "docs/hub/.schema/engine.md" }, + { + "schema": "src/hub/.schema/linterhub.output.json", + "cs": "src/core/Schema/LinterhubOutputSchema.cs", + "md": "docs/hub/.schema/linterhub.output.md" + }, { "schema": "src/hub/.schema/args.json", "md": "docs/hub/.schema/args.md" diff --git a/src/cli/Properties/launchSettings.json b/src/cli/Properties/launchSettings.json index 2ba8b00..3f7f98d 100644 --- a/src/cli/Properties/launchSettings.json +++ b/src/cli/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "cli": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/marp --folder=coffee/classes --linter=coffeelint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/angular/aio/content/examples --folder=toh-pt5/src/app --engine=colorguard" }, "catalog": { "commandName": "Project", @@ -10,111 +10,111 @@ }, "coffeelint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/marp --file=coffee/classes/mds_main_menu.coffee --linter=coffeelint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/marp --file=coffee/classes/mds_main_menu.coffee --engine=coffeelint" }, "coffeelint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/marp --folder=coffee/classes --linter=coffeelint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/marp --folder=coffee/classes --engine=coffeelint" }, "coffeelint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/marp --linter=coffeelint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/marp --engine=coffeelint" }, "csslint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery-ui/themes/base --file=all.css --linter=csslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery-ui/themes/base --file=all.css --engine=csslint" }, "csslint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/Font-Awesome --folder=css --linter=csslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/Font-Awesome --folder=css --engine=csslint" }, "csslint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/Font-Awesome --linter=csslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/Font-Awesome --engine=csslint" }, "eslint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --linter=eslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --engine=eslint" }, "eslint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --folder=include --linter=eslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --folder=include --engine=eslint" }, "eslint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --linter=eslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --engine=eslint" }, "htmlhint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=xmlstyle.html --linter=htmlhint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=xmlstyle.html --engine=htmlhint" }, "htmlhint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery --folder=test/data/core --linter=htmlhint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery --folder=test/data/core --engine=htmlhint" }, "htmlhint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery/test/data --linter=htmlhint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery/test/data --engine=htmlhint" }, "jshint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --linter=jshint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --engine=jshint" }, "jshint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --folder=include --linter=jshint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --folder=include --engine=jshint" }, "jshint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --linter=jshint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --engine=jshint" }, "jslint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --linter=jslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --engine=jslint" }, "jslint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery --folder=src/core/var --linter=jslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery --folder=src/core/var --engine=jslint" }, "jslint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery/src/core/var --linter=jslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/jquery/src/core/var --engine=jslint" }, "standard-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --linter=standard" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --file=include/styleguide.js --engine=standard" }, "standard-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --folder=include --linter=standard" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --folder=include --engine=standard" }, "standard-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --linter=standard" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/styleguide --engine=standard" }, "stylelint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/Font-Awesome --folder=less --file=core.less --linter=stylelint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/Font-Awesome --folder=less --file=core.less --engine=stylelint" }, "stylelint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/bulma --folder=sass/base --linter=stylelint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/bulma --folder=sass/base --engine=stylelint" }, "stylelint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/bulma/sass/grid --linter=stylelint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/bulma/sass/grid --engine=stylelint" }, "tslint-file": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/angular --folder=integration/hello_world__closure/src --file=main.ts --linter=tslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/angular --folder=integration/hello_world__closure/src --file=main.ts --engine=tslint" }, "tslint-folder": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/angular --folder=integration/hello_world__closure/src --linter=tslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/angular --folder=integration/hello_world__closure/src --engine=tslint" }, "tslint-project": { "commandName": "Project", - "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/angular/integration/hello_world__closure --linter=tslint" + "commandLineArgs": "--mode=Analyze --project=../../test/linterhub-tests/angular/integration/hello_world__closure --engine=tslint" } } } \ No newline at end of file diff --git a/src/core/Runtime/EngineRunner.cs b/src/core/Runtime/EngineRunner.cs index c2a404c..4a79dee 100644 --- a/src/core/Runtime/EngineRunner.cs +++ b/src/core/Runtime/EngineRunner.cs @@ -1,11 +1,13 @@ namespace Linterhub.Core.Runtime { + using Exceptions; using Extensions; using Schema; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; + using Result = Schema.LinterhubOutputSchema.ResultType; public class EngineRunner { @@ -16,14 +18,14 @@ public EngineRunner(EngineWrapper wrapper) engineRunner = wrapper; } - public List RunAnalyze( + public LinterhubOutputSchema RunAnalyze( List contexts, string project, string directory, string file, LinterhubConfigSchema config = null) { - var results = new List(); + var output = new LinterhubOutputSchema(); var n_contexts = new List(); string stdin = ""; @@ -75,63 +77,97 @@ public EngineRunner(EngineWrapper wrapper) Parallel.ForEach(n_contexts, context => { - - var res = engineRunner.RunAnalysis(context, stdin); - var current = res.DeserializeAsJson(); - lock (results) + var error = new LinterhubOutputSchema.ErrorType() + { + Code = 0 + }; + EngineOutputSchema parsedOutput = null; + try + { + var res = engineRunner.RunAnalysis(context, stdin); + parsedOutput = res.DeserializeAsJson(); + } + catch (EngineException e) + { + error.Code = (int)e.exitCode; + error.Title = e.Message; + } + lock (output) { - foreach (var output in current) + var result = new EngineOutputSchema(); + if (error.Code == 0) { - if (!string.IsNullOrEmpty(directory) && output.Path != string.Empty) + foreach (var current in parsedOutput) { - var directoryPrefix = Path.GetFullPath(directory).Replace(Path.GetFullPath(project), string.Empty) - .TrimStart('/') - .TrimStart('\\') - .Replace("/", "\\"); - - if (!output.Path.Contains(directoryPrefix)) + if (!string.IsNullOrEmpty(directory) && current.Path != string.Empty) { - output.Path = Path.Combine(Path.GetFullPath(directory), output.Path); + var directoryPrefix = Path.GetFullPath(directory).Replace(Path.GetFullPath(project), string.Empty) + .TrimStart('/') + .TrimStart('\\') + .Replace("/", "\\"); + + if (!current.Path.Contains(directoryPrefix)) + { + current.Path = Path.Combine(Path.GetFullPath(directory), current.Path); + } } - } - output.Path = output - .Path - .Replace(project, string.Empty) - .Replace(Path.GetFullPath(project), string.Empty) - .TrimStart('/') - .TrimStart('\\') - .Replace("/", "\\"); + current.Path = current + .Path + .Replace(project, string.Empty) + .Replace(Path.GetFullPath(project), string.Empty) + .TrimStart('/') + .TrimStart('\\') + .Replace("/", "\\"); + } + result.AddRange(parsedOutput.Where(x => x.Path != string.Empty)); + } - var req = results.Where(x => x.Path == output.Path); - if (req.Count() > 0) + if (result.Count != 0) { + var exists = output.Find(x => x.Engine == context.Specification.Schema.Name); + if (exists != null) { - req.First().Messages.AddRange(output.Messages); - req.First().Messages = req.First().Messages.OrderBy(x => x.Line).ThenBy(x => x.Column).ThenBy(x => x.RuleId).ToList(); + if (exists.Error == null && error.Code == 0) + { + exists.Result.AddRange(result); + } + else if (error.Code != 0) + { + exists.Error = error; + } + } - else if (output.Path != string.Empty) + else { - results.Add(output); + output.Add(new Result() + { + Result = result, + Engine = context.Specification.Schema.Name, + Error = error.Code == 0 ? null : error + }); } } + } }); if(config != null) { - results = results.Select(x => - { - x.Messages = x.Messages.Where(m => - { - return !(config.Ignore.Find(r => x.Path.Contains(r.Mask)) != null || config.Ignore.Find(r => x.Path.Contains(r.Mask) && m.RuleId == r.RuleId) != null || - config.Ignore.Find(r => x.Path.Contains(r.Mask) && m.Line == r.Line) != null); - }).ToList(); - return x; - }).ToList(); + output.ForEach(x => { + x.Result.ForEach(y => { + y.Messages = y.Messages.Where(m => + { + return !(config.Ignore.Find(r => y.Path.Contains(r.Mask)) != null || config.Ignore.Find(r => y.Path.Contains(r.Mask) && m.RuleId == r.RuleId) != null || + config.Ignore.Find(r => y.Path.Contains(r.Mask) && m.Line == r.Line) != null); + }).OrderBy(z => z.Line).ThenBy(z => z.Column).ThenBy(z => z.RuleId).ToList(); + }); + x.Result.Sort((a,b) => a.Path.CompareTo(b.Path)); + }); + output.Sort((a,b) => a.Engine.CompareTo(b.Engine)); } - return results.OrderBy((x) => x.Path).ToList(); + return output; } } diff --git a/src/core/Runtime/EngineWrapper.cs b/src/core/Runtime/EngineWrapper.cs index 970221e..867cea1 100644 --- a/src/core/Runtime/EngineWrapper.cs +++ b/src/core/Runtime/EngineWrapper.cs @@ -54,7 +54,7 @@ public string RunAnalysis(EngineWrapper.Context context, string stdin = "") var command = CommandFactory.GetAnalyzeCommand(context); var result = Run(context, command, successCode: context.Specification.Schema.SuccessCode ?? 0, stdin: context.Stdin == Context.stdinType.UseWithEngine ? stdin : string.Empty) - .DeserializeAsJson() + .DeserializeAsJson() .Select((file) => { var dic = context.RunOptions.Where(x => x.Key == "file://{stdin}"); if (dic.Count() != 0) diff --git a/src/core/Schema/EngineOutputSchema.cs b/src/core/Schema/EngineOutputSchema.cs index 1e899a8..bacaf15 100644 --- a/src/core/Schema/EngineOutputSchema.cs +++ b/src/core/Schema/EngineOutputSchema.cs @@ -7,7 +7,7 @@ namespace Linterhub.Core.Schema /// /// Engine output is an array of analysis results /// - public class EngineOutputSchema + public class EngineOutputSchema : List { /// diff --git a/src/core/Schema/EngineVersionSchema.cs b/src/core/Schema/EngineVersionSchema.cs index 78ea107..f2bc703 100644 --- a/src/core/Schema/EngineVersionSchema.cs +++ b/src/core/Schema/EngineVersionSchema.cs @@ -7,7 +7,7 @@ namespace Linterhub.Core.Schema /// /// Engine version output is an array of objects describing the state of the engines /// - public class EngineVersionSchema + public class EngineVersionSchema : List { /// diff --git a/src/core/Schema/LinterhubOutputSchema.cs b/src/core/Schema/LinterhubOutputSchema.cs new file mode 100644 index 0000000..346b839 --- /dev/null +++ b/src/core/Schema/LinterhubOutputSchema.cs @@ -0,0 +1,57 @@ +namespace Linterhub.Core.Schema +{ + using System.Collections.Generic; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// Linterhub output is an array of engines results + /// + public class LinterhubOutputSchema : List + { + + /// + /// The engine result + /// + public class ResultType + { + + /// + /// Gets or sets the engine name that performed analysis + /// + public string Engine { get; set; } + + /// + /// Gets or sets the analysis result + /// + public EngineOutputSchema Result { get; set; } + + /// + /// Gets or sets the problem definition if analysis is not possible + /// + public ErrorType Error { get; set; } + } + + /// + /// The problem definition if analysis is not possible + /// + public class ErrorType + { + + /// + /// Gets or sets the error code + /// + public int? Code { get; set; } + + /// + /// Gets or sets the error title + /// + public string Title { get; set; } + + /// + /// Gets or sets the error decription + /// + public string Description { get; set; } + } + } +} \ No newline at end of file diff --git a/src/hub/.schema/linterhub.output.json b/src/hub/.schema/linterhub.output.json new file mode 100644 index 0000000..e1e25aa --- /dev/null +++ b/src/hub/.schema/linterhub.output.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "This document describes the structure of linterhub output", + "description": "Linterhub output is an array of engines results", + "type": "array", + "items": { + "$ref": "#/definitions/result" + }, + "definitions": { + "result": { + "type": "object", + "description": "The engine result", + "properties": { + "engine": { + "type": "string", + "description": "The engine name that performed analysis" + }, + "result": { + "type": "object", + "description": "The analysis result" + }, + "error": { + "type": "object", + "description": "The problem definition if analysis is not possible", + "properties": { + "code": { + "type": "integer", + "description": "The error code" + }, + "title": { + "type": "string", + "description": "The error title" + }, + "description": { + "type": "string", + "description": "The error decription" + } + }, + "required": [ + "code", + "title" + ] + } + }, + "required": [ + "engine", + "results" + ] + } + } +} \ No newline at end of file diff --git a/test b/test index ea9a39f..46e21d8 160000 --- a/test +++ b/test @@ -1 +1 @@ -Subproject commit ea9a39f69add4aca2f31e3f1c4e09455c384f7f3 +Subproject commit 46e21d82451bfb8ac8aa0c09dbeff9a03a78f2f4