1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 | const Filter = require('broccoli-persistent-filter');
|
6 | const md5Hex = require('md5-hex');
|
7 | const stringify = require('json-stable-stringify');
|
8 | const chalk = require('chalk');
|
9 | const Linter = require('ember-template-lint');
|
10 | const debug = require('debug')('template-lint:broccoli');
|
11 | const projectLocalizationAddon = require('./lib/utils/project-localization-framework');
|
12 | const testGenerators = require('aot-test-generators');
|
13 | const testGeneratorNames = Object.keys(testGenerators);
|
14 | const concat = require('broccoli-concat');
|
15 |
|
16 | function TemplateLinter(inputNode, _options) {
|
17 | if (!(this instanceof TemplateLinter)) { return new TemplateLinter(inputNode, _options); }
|
18 |
|
19 | let options = _options || {};
|
20 | if (!options.hasOwnProperty('persist')) {
|
21 | options.persist = true;
|
22 | }
|
23 |
|
24 | Filter.call(this, inputNode, {
|
25 | annotation: options.annotation,
|
26 | persist: options.persist
|
27 | });
|
28 |
|
29 |
|
30 | this.options = options;
|
31 | this._console = this.options.console || console;
|
32 | this._templatercConfig = undefined;
|
33 |
|
34 | if (this.options.testGenerator) {
|
35 | let testGenerator = testGenerators[this.options.testGenerator];
|
36 | if (!testGenerator) {
|
37 | throw new Error(`No test generator found for "testGenerator: ${this.options.testGenerator}"`);
|
38 | }
|
39 |
|
40 | this._testGenerator = testGenerator;
|
41 | }
|
42 |
|
43 | this.linter = new Linter(options);
|
44 |
|
45 | debug('Linter config: %s', JSON.stringify(this.linter.config));
|
46 |
|
47 | this.issueLocalizationWarningIfNeeded();
|
48 | }
|
49 |
|
50 | TemplateLinter.prototype = Object.create(Filter.prototype);
|
51 | TemplateLinter.prototype.constructor = TemplateLinter;
|
52 |
|
53 | TemplateLinter.prototype.extensions = ['hbs'];
|
54 | TemplateLinter.prototype.targetExtension = 'template.lint-test.js';
|
55 |
|
56 | TemplateLinter.prototype.baseDir = function() {
|
57 | return __dirname;
|
58 | };
|
59 |
|
60 | TemplateLinter.prototype.cacheKeyProcessString = function(string, relativePath) {
|
61 | return md5Hex([
|
62 | stringify(this.linter.config),
|
63 | this.options.testGenerator || '',
|
64 | this.options.groupName || '',
|
65 | string,
|
66 | relativePath
|
67 | ]);
|
68 | };
|
69 |
|
70 | TemplateLinter.prototype.build = function () {
|
71 | let self = this;
|
72 | self._errors = [];
|
73 |
|
74 | return Filter.prototype.build.apply(this, arguments)
|
75 | .finally(function() {
|
76 | if (self._errors.length > 0) {
|
77 | let label = ' Template Linting Error' + (self._errors.length > 1 ? 's' : '');
|
78 | self._console.log('\n' + self._errors.join('\n'));
|
79 | self._console.log(chalk.yellow('===== ' + self._errors.length + label + '\n'));
|
80 | }
|
81 | });
|
82 | };
|
83 |
|
84 | TemplateLinter.prototype.convertErrorToDisplayMessage = function(error) {
|
85 | let message = error.rule + ': ' + error.message + ' (' + error.moduleId;
|
86 |
|
87 | if (error.line && error.column) {
|
88 | message = message + ' @ L' + error.line + ':C' + error.column;
|
89 | }
|
90 |
|
91 | message = message + ')';
|
92 |
|
93 | if (error.source) {
|
94 | message = message + ': \n`' + error.source + '`';
|
95 | }
|
96 |
|
97 | return message;
|
98 | };
|
99 |
|
100 | TemplateLinter.prototype.processString = function(contents, relativePath) {
|
101 | let errors = this.linter.verify({
|
102 | source: contents,
|
103 | moduleId: relativePath.slice(0, -4)
|
104 | });
|
105 | errors = errors.filter(function(error) {
|
106 | return error.severity > 1;
|
107 | });
|
108 | let passed = errors.length === 0;
|
109 | let errorDisplay = errors.map(function(error) {
|
110 | return this.convertErrorToDisplayMessage(error);
|
111 | }, this)
|
112 | .join('\n');
|
113 |
|
114 | let output = '';
|
115 | if (this._testGenerator) {
|
116 | if (this.options.groupName) {
|
117 | output = this._testGenerator.test(relativePath, passed,
|
118 | `${relativePath} should pass TemplateLint.\n\n${errorDisplay}`);
|
119 |
|
120 | } else {
|
121 | output = [
|
122 | this._testGenerator.suiteHeader(`TemplateLint | ${relativePath}`),
|
123 | this._testGenerator.test('should pass TemplateLint', passed,
|
124 | `${relativePath} should pass TemplateLint.\n\n${errorDisplay}`),
|
125 | this._testGenerator.suiteFooter()
|
126 | ].join('');
|
127 | }
|
128 | }
|
129 |
|
130 | debug('Found %s errors for %s with \ncontents: \n%s\nerrors: \n%s', errors.length, relativePath, contents, errorDisplay);
|
131 |
|
132 | return {
|
133 | errors: errors,
|
134 | output: output
|
135 | };
|
136 | };
|
137 |
|
138 | TemplateLinter.prototype.postProcess = function(results) {
|
139 | let errors = results.errors;
|
140 |
|
141 | for (let i = 0; i < errors.length; i++) {
|
142 | let errorDisplay = this.convertErrorToDisplayMessage(errors[i]);
|
143 | this._errors.push(chalk.red(errorDisplay));
|
144 | }
|
145 |
|
146 | return results;
|
147 | };
|
148 |
|
149 | TemplateLinter.prototype.issueLocalizationWarningIfNeeded = function() {
|
150 | if ('bare-strings' in this.linter.config.rules) {
|
151 | return;
|
152 | }
|
153 |
|
154 | let project = this.options.project;
|
155 | if (!project) {
|
156 | return;
|
157 | }
|
158 |
|
159 | let addon = projectLocalizationAddon(project);
|
160 |
|
161 | if (addon) {
|
162 | this._console.log(chalk.yellow(
|
163 | 'The `bare-strings` rule must be configured when using a localization framework (`' + addon.name + '`). To prevent this warning, add the following to your `.template-lintrc.js`:\n\n rules: {\n \'bare-strings\': true\n }'
|
164 | ));
|
165 | }
|
166 | };
|
167 |
|
168 | TemplateLinter.create = function(inputNode, options) {
|
169 | options = options || {};
|
170 |
|
171 | if (!options.groupName) {
|
172 | return new TemplateLinter(inputNode, options);
|
173 | }
|
174 |
|
175 | if (testGeneratorNames.indexOf(options.testGenerator) === -1) {
|
176 | throw new Error(`The "groupName" options can only be used with a "testGenerator" option of: ${testGeneratorNames}`);
|
177 | }
|
178 |
|
179 | let testGenerator = testGenerators[options.testGenerator];
|
180 |
|
181 | let headerName = 'TemplateLint';
|
182 | if (options.groupName !== 'templates') {
|
183 | headerName += ` | ${options.groupName}`;
|
184 | }
|
185 |
|
186 | let header = testGenerator.suiteHeader(headerName);
|
187 | let footer = testGenerator.suiteFooter();
|
188 |
|
189 | let lint = new TemplateLinter(inputNode, options);
|
190 |
|
191 | return concat(lint, {
|
192 | outputFile: `/${options.groupName}.template.lint-test.js`,
|
193 | header,
|
194 | inputFiles: ['**/*.template.lint-test.js'],
|
195 | footer,
|
196 | sourceMapConfig: { enabled: false },
|
197 | allowNone: true
|
198 | });
|
199 | };
|
200 |
|
201 | module.exports = TemplateLinter;
|