eslint-plugin-ember-template-lint 0.18.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config/base.js +9 -0
- package/lib/config/index.js +21 -0
- package/lib/config-legacy/base.js +7 -0
- package/lib/config-legacy/index.js +21 -0
- package/lib/ember-template-lint/config.js +35 -0
- package/lib/ember-template-lint/info.js +133 -0
- package/lib/ember-template-lint/worker.js +13 -0
- package/lib/index.js +2 -6
- package/lib/parser/hbs-parser.js +57 -0
- package/lib/rules/hbs-worker.js +17 -4
- package/lib/rules/lint.js +8 -1
- package/lib/utils/document.js +100 -0
- package/package.json +16 -14
@@ -0,0 +1,21 @@
|
|
1
|
+
const info = require('../ember-template-lint/info');
|
2
|
+
const base = require('./base');
|
3
|
+
|
4
|
+
const configs = {};
|
5
|
+
|
6
|
+
Object.entries(info.configs).forEach(([name, config]) => {
|
7
|
+
Object.entries(config.rules).forEach(([name, conf]) => {
|
8
|
+
if (typeof conf == 'boolean') {
|
9
|
+
config.rules[name] = [conf ? 'error' : 'off'];
|
10
|
+
}
|
11
|
+
});
|
12
|
+
configs[name] = {...base, rules: config.rules};
|
13
|
+
});
|
14
|
+
configs['config'] = {
|
15
|
+
...base,
|
16
|
+
rules: info.configuredRules,
|
17
|
+
overrides: info.configuredOverrides,
|
18
|
+
};
|
19
|
+
|
20
|
+
module.exports = configs;
|
21
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
const info = require('../ember-template-lint/info');
|
2
|
+
const base = require('./base');
|
3
|
+
|
4
|
+
const configs = {};
|
5
|
+
|
6
|
+
Object.entries(info.configs).forEach(([name, config]) => {
|
7
|
+
Object.entries(config.rules).forEach(([name, conf]) => {
|
8
|
+
if (typeof conf == 'boolean') {
|
9
|
+
config.rules[name] = [conf ? 'error' : 'off'];
|
10
|
+
}
|
11
|
+
});
|
12
|
+
configs[name] = {...base, rules: config.rules};
|
13
|
+
});
|
14
|
+
configs['config'] = {
|
15
|
+
...base,
|
16
|
+
rules: info.configuredRules,
|
17
|
+
overrides: info.configuredOverrides,
|
18
|
+
};
|
19
|
+
|
20
|
+
module.exports = configs;
|
21
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
const templateLintConfig = {
|
2
|
+
rules: {},
|
3
|
+
plugins: [],
|
4
|
+
overrides: [],
|
5
|
+
};
|
6
|
+
|
7
|
+
let current = templateLintConfig;
|
8
|
+
|
9
|
+
function registerRule(name, config) {
|
10
|
+
current.rules[name] = Array.isArray(config) ? config[0] : config;
|
11
|
+
}
|
12
|
+
|
13
|
+
function registerPlugin(name) {
|
14
|
+
current.plugins.push(name);
|
15
|
+
}
|
16
|
+
|
17
|
+
function startOverride(files) {
|
18
|
+
current = {
|
19
|
+
files,
|
20
|
+
rules: {}
|
21
|
+
};
|
22
|
+
templateLintConfig.overrides.push(current);
|
23
|
+
}
|
24
|
+
|
25
|
+
function finishOverride() {
|
26
|
+
current = templateLintConfig;
|
27
|
+
}
|
28
|
+
|
29
|
+
module.exports = {
|
30
|
+
registerRule,
|
31
|
+
registerPlugin,
|
32
|
+
startOverride,
|
33
|
+
finishOverride,
|
34
|
+
templateLintConfig
|
35
|
+
};
|
@@ -0,0 +1,133 @@
|
|
1
|
+
const synckit = require('synckit');
|
2
|
+
const syncFn = synckit.createSyncFn(require.resolve('./worker'));
|
3
|
+
const { runTemplateLint } = require('../rules/lint');
|
4
|
+
const { registerRule, templateLintConfig } = require('../ember-template-lint/config');
|
5
|
+
|
6
|
+
const lintWithEslintConfigs = syncFn({ config: templateLintConfig });
|
7
|
+
const lintConfigs = syncFn();
|
8
|
+
|
9
|
+
const activeRules = new Map();
|
10
|
+
const allMessages = {};
|
11
|
+
|
12
|
+
const reporter = {
|
13
|
+
setup(context) {
|
14
|
+
this.getSourceCode = context.getSourceCode;
|
15
|
+
this.getPhysicalFilename = context.getPhysicalFilename;
|
16
|
+
},
|
17
|
+
report(message) {
|
18
|
+
message.meta = {
|
19
|
+
fixable: 'code'
|
20
|
+
};
|
21
|
+
allMessages[message.rule] = allMessages[message.rule] || [];
|
22
|
+
allMessages[message.rule].push(message);
|
23
|
+
}
|
24
|
+
};
|
25
|
+
|
26
|
+
class Rule {
|
27
|
+
|
28
|
+
constructor(name) {
|
29
|
+
this.create = this.create.bind(this);
|
30
|
+
this.name = name;
|
31
|
+
this.meta = {
|
32
|
+
fixable: 'code'
|
33
|
+
};
|
34
|
+
}
|
35
|
+
create(context) {
|
36
|
+
const rule = this;
|
37
|
+
let options = context.options;
|
38
|
+
if (options.length === 0) {
|
39
|
+
options = true;
|
40
|
+
}
|
41
|
+
registerRule(this.name, options);
|
42
|
+
const visitor = {
|
43
|
+
enter(node) {
|
44
|
+
if (!activeRules.get(node)) {
|
45
|
+
activeRules.set(node, 0);
|
46
|
+
reporter.setup(context);
|
47
|
+
runTemplateLint(node, reporter);
|
48
|
+
}
|
49
|
+
activeRules.set(node, activeRules.get(node) + 1);
|
50
|
+
},
|
51
|
+
exit(node) {
|
52
|
+
const messages = allMessages[rule.name] || [];
|
53
|
+
messages.forEach(m => context.report(m));
|
54
|
+
activeRules.set(node, activeRules.get(node) - 1);
|
55
|
+
allMessages[rule.name] = [];
|
56
|
+
},
|
57
|
+
};
|
58
|
+
return {
|
59
|
+
'Program': (node) => node.isHbs && visitor.enter(node),
|
60
|
+
'Program:exit': (node) => node.isHbs && visitor.exit(node),
|
61
|
+
'GlimmerTemplate': visitor.enter,
|
62
|
+
'GlimmerTemplate:exit': visitor.exit,
|
63
|
+
};
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
function createRules(rules) {
|
68
|
+
const created = rules.map(r => new Rule(r));
|
69
|
+
const map = {};
|
70
|
+
created.forEach(r => {
|
71
|
+
map[r.name] = r;
|
72
|
+
});
|
73
|
+
return map;
|
74
|
+
}
|
75
|
+
|
76
|
+
const configs = {
|
77
|
+
...lintWithEslintConfigs.configs,
|
78
|
+
...lintConfigs.configs
|
79
|
+
};
|
80
|
+
|
81
|
+
const rules = [...new Set([...lintConfigs.rules, ...lintWithEslintConfigs.rules])];
|
82
|
+
|
83
|
+
delete configs.recommended.overrides;
|
84
|
+
|
85
|
+
Object.values(configs).forEach((config) => {
|
86
|
+
const rules = {};
|
87
|
+
Object.entries(config.rules).forEach(([rule, conf]) => {
|
88
|
+
rules['ember-template-lint/' + rule] = conf;
|
89
|
+
});
|
90
|
+
config.rules = rules;
|
91
|
+
});
|
92
|
+
|
93
|
+
// enable all rules
|
94
|
+
const configuredRules = {};
|
95
|
+
|
96
|
+
for (const rule of rules) {
|
97
|
+
configuredRules['ember-template-lint/' + rule] = [];
|
98
|
+
configuredRules['ember-template-lint/' + rule].push(2);
|
99
|
+
configuredRules['ember-template-lint/' + rule].push({ __placeholder__: true });
|
100
|
+
}
|
101
|
+
|
102
|
+
Object.entries(lintConfigs.configuredRules).forEach(([name, conf]) => {
|
103
|
+
configuredRules['ember-template-lint/' + name] = [];
|
104
|
+
configuredRules['ember-template-lint/' + name].push(conf.severity);
|
105
|
+
if (typeof conf.config !== 'boolean') {
|
106
|
+
configuredRules['ember-template-lint/' + name].push(conf.config);
|
107
|
+
}
|
108
|
+
});
|
109
|
+
|
110
|
+
const configuredOverrides = [];
|
111
|
+
|
112
|
+
for (const configuredOverride of lintConfigs.configuredOverrides) {
|
113
|
+
const configuredRules = {};
|
114
|
+
Object.entries(configuredOverride.rules).forEach(([name, conf]) => {
|
115
|
+
configuredRules['ember-template-lint/' + name] = [];
|
116
|
+
configuredRules['ember-template-lint/' + name].push(conf.severity);
|
117
|
+
if (typeof conf.config !== 'boolean') {
|
118
|
+
configuredRules['ember-template-lint/' + name].push(conf.config);
|
119
|
+
}
|
120
|
+
});
|
121
|
+
configuredOverrides.push({
|
122
|
+
files: configuredOverride.files,
|
123
|
+
rules: configuredRules
|
124
|
+
});
|
125
|
+
}
|
126
|
+
|
127
|
+
|
128
|
+
module.exports = {
|
129
|
+
configs: configs,
|
130
|
+
rules: createRules(rules),
|
131
|
+
configuredRules,
|
132
|
+
configuredOverrides
|
133
|
+
};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
const { runAsWorker } = require('synckit');
|
2
|
+
|
3
|
+
runAsWorker(async (options) => {
|
4
|
+
const Lint = await import('ember-template-lint');
|
5
|
+
const lint = new Lint.default(options);
|
6
|
+
await lint.loadConfig();
|
7
|
+
return {
|
8
|
+
configs: lint.config.loadedConfigurations,
|
9
|
+
rules: Object.keys(lint.config.loadedRules),
|
10
|
+
configuredRules: lint.config.rules,
|
11
|
+
configuredOverrides: lint.config.overrides,
|
12
|
+
};
|
13
|
+
});
|
package/lib/index.js
CHANGED
@@ -8,10 +8,9 @@
|
|
8
8
|
// Requirements
|
9
9
|
//------------------------------------------------------------------------------
|
10
10
|
|
11
|
-
const configs = require('./config');
|
12
|
-
const base = require('./config/base');
|
11
|
+
const configs = require('./config-legacy');
|
12
|
+
const base = require('./config-legacy/base');
|
13
13
|
const templateRules = require('./ember-template-lint/info');
|
14
|
-
const processor = require('./processor');
|
15
14
|
|
16
15
|
//------------------------------------------------------------------------------
|
17
16
|
// Plugin Definition
|
@@ -25,8 +24,5 @@ module.exports = {
|
|
25
24
|
configs: {
|
26
25
|
base,
|
27
26
|
...configs
|
28
|
-
},
|
29
|
-
processors: {
|
30
|
-
'noop': processor,
|
31
27
|
}
|
32
28
|
};
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
class Scope {
|
3
|
+
type = 'global';
|
4
|
+
variables = [];
|
5
|
+
through= [];
|
6
|
+
set = new Map();
|
7
|
+
upper = null;
|
8
|
+
childScopes = [];
|
9
|
+
references = [];
|
10
|
+
block = null;
|
11
|
+
}
|
12
|
+
|
13
|
+
class ScopeManager {
|
14
|
+
globalScope = new Scope();
|
15
|
+
scopes = [this.globalScope];
|
16
|
+
|
17
|
+
acquire() {
|
18
|
+
return;
|
19
|
+
}
|
20
|
+
|
21
|
+
getDeclaredVariables() {
|
22
|
+
return [];
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
module.exports = {
|
27
|
+
parseForESLint(code) {
|
28
|
+
const comments = [];
|
29
|
+
const types = new Set(['Program']);
|
30
|
+
const ast = {};
|
31
|
+
ast.body = [];
|
32
|
+
ast.tokens = [ast];
|
33
|
+
ast.range = [0, code.length];
|
34
|
+
const lines = code.split('\n');
|
35
|
+
ast.loc = {
|
36
|
+
start: {
|
37
|
+
line: 1,
|
38
|
+
column: 1
|
39
|
+
},
|
40
|
+
end: {
|
41
|
+
line: lines.length,
|
42
|
+
column: lines[lines.length - 1].length
|
43
|
+
}
|
44
|
+
};
|
45
|
+
ast.comments = comments;
|
46
|
+
const visitorKeys = {};
|
47
|
+
types.forEach((t) => {
|
48
|
+
visitorKeys[t] = [];
|
49
|
+
});
|
50
|
+
ast.type = 'Program';
|
51
|
+
ast.isHbs = true;
|
52
|
+
ast.contents = code;
|
53
|
+
const scope = new ScopeManager();
|
54
|
+
scope.globalScope.block = ast;
|
55
|
+
return { ast, scopeManager: scope, visitorKeys };
|
56
|
+
}
|
57
|
+
};
|
package/lib/rules/hbs-worker.js
CHANGED
@@ -113,12 +113,23 @@ async function _applyFixes(options, results, columnOffset) {
|
|
113
113
|
return currentSource;
|
114
114
|
}
|
115
115
|
|
116
|
-
|
116
|
+
async function runTemplateLint(filename, text, options, columnOffset=0) {
|
117
117
|
const Lint = await import('ember-template-lint');
|
118
118
|
const lint = new Lint.default(options);
|
119
119
|
process.env.emberTemplateLintFileName = filename;
|
120
120
|
process.env.emberTemplateLintFixMode = false;
|
121
121
|
await lint.loadConfig();
|
122
|
+
const processedConf = lint.config;
|
123
|
+
delete options.config;
|
124
|
+
await lint.loadConfig();
|
125
|
+
lint.config.rules = {
|
126
|
+
...lint.config.rules,
|
127
|
+
...processedConf.rules
|
128
|
+
};
|
129
|
+
lint.config.overrides = [
|
130
|
+
...lint.config.overrides,
|
131
|
+
...processedConf.overrides
|
132
|
+
];
|
122
133
|
let fileConfig = lint._moduleStatusCache.getConfigForFile(options);
|
123
134
|
if (fileConfig.loadedRules['prettier']) {
|
124
135
|
const rule = fileConfig.loadedRules['prettier'].prototype;
|
@@ -131,14 +142,16 @@ runAsWorker(async (filename, text, options, columnOffset=0) => {
|
|
131
142
|
|
132
143
|
const messages = await lint.verify({
|
133
144
|
source: text,
|
134
|
-
filePath: filename
|
145
|
+
filePath: filename,
|
135
146
|
});
|
136
147
|
process.env.emberTemplateLintFixMode = true;
|
137
148
|
await _applyFixes.call(lint,{
|
138
149
|
source: text,
|
139
|
-
filePath: filename
|
150
|
+
filePath: filename,
|
140
151
|
}, messages, columnOffset);
|
141
152
|
return {
|
142
153
|
messages,
|
143
154
|
};
|
144
|
-
}
|
155
|
+
}
|
156
|
+
|
157
|
+
runAsWorker(runTemplateLint);
|
package/lib/rules/lint.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
'use strict';
|
2
2
|
const synckit = require('synckit');
|
3
|
+
const path = require('path');
|
3
4
|
const DocumentLines = require('../utils/document');
|
4
5
|
const { templateLintConfig } = require('../ember-template-lint/config');
|
5
6
|
|
@@ -22,7 +23,13 @@ function runTemplateLint(node, context) {
|
|
22
23
|
|
23
24
|
try {
|
24
25
|
const syncFn = synckit.createSyncFn(require.resolve('./hbs-worker'));
|
25
|
-
const
|
26
|
+
const config = JSON.parse(JSON.stringify(templateLintConfig));
|
27
|
+
for (const key of Object.keys(config.rules)) {
|
28
|
+
if (config.rules[key].__placeholder__) {
|
29
|
+
delete config.rules[key];
|
30
|
+
}
|
31
|
+
}
|
32
|
+
const response = syncFn(filename, text, { config, workingDir: path.dirname(filename) }, columnOffset);
|
26
33
|
const lintMessages = response.messages;
|
27
34
|
lintMessages.forEach((m) => {
|
28
35
|
if (m.fix) {
|
@@ -0,0 +1,100 @@
|
|
1
|
+
/**
|
2
|
+
* @typedef {{ line: number; character: number }} Position
|
3
|
+
*/
|
4
|
+
|
5
|
+
// Helper class to convert line/column from and to offset
|
6
|
+
// taken and adapt from https://github.com/typed-ember/glint/blob/main/packages/core/src/language-server/util/position.ts
|
7
|
+
class DocumentLines {
|
8
|
+
/**
|
9
|
+
* @param {string} contents
|
10
|
+
*/
|
11
|
+
constructor(contents) {
|
12
|
+
this.lineStarts = computeLineStarts(contents);
|
13
|
+
}
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @param {Position} position
|
17
|
+
* @return {number}
|
18
|
+
*/
|
19
|
+
positionToOffset(position) {
|
20
|
+
const { line, character } = position;
|
21
|
+
return this.lineStarts[line] + character;
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
*
|
26
|
+
* @param {number} position
|
27
|
+
* @return {Position}
|
28
|
+
*/
|
29
|
+
offsetToPosition(position) {
|
30
|
+
const lineStarts = this.lineStarts;
|
31
|
+
let line = 0;
|
32
|
+
while (line + 1 < lineStarts.length && lineStarts[line + 1] <= position) {
|
33
|
+
line++;
|
34
|
+
}
|
35
|
+
const character = position - lineStarts[line];
|
36
|
+
return { line, character };
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* @returns {number[]}
|
42
|
+
* @param {string} text
|
43
|
+
*/
|
44
|
+
function computeLineStarts(text) {
|
45
|
+
const result = [];
|
46
|
+
let pos = 0;
|
47
|
+
let lineStart = 0;
|
48
|
+
while (pos < text.length) {
|
49
|
+
const ch = text.codePointAt(pos);
|
50
|
+
pos++;
|
51
|
+
switch (ch) {
|
52
|
+
case 13 /* carriageReturn */: {
|
53
|
+
if (text.codePointAt(pos) === 10 /* lineFeed */) {
|
54
|
+
pos++;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
// falls through
|
58
|
+
case 10 /* lineFeed */: {
|
59
|
+
result.push(lineStart);
|
60
|
+
lineStart = pos;
|
61
|
+
break;
|
62
|
+
}
|
63
|
+
default: {
|
64
|
+
if (ch > 127 /* maxAsciiCharacter */ && isLineBreak(ch)) {
|
65
|
+
result.push(lineStart);
|
66
|
+
lineStart = pos;
|
67
|
+
}
|
68
|
+
break;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
result.push(lineStart);
|
73
|
+
return result;
|
74
|
+
}
|
75
|
+
|
76
|
+
/* istanbul ignore next */
|
77
|
+
/**
|
78
|
+
* @param {number} ch
|
79
|
+
* @return {boolean}
|
80
|
+
*/
|
81
|
+
function isLineBreak(ch) {
|
82
|
+
// ES5 7.3:
|
83
|
+
// The ECMAScript line terminator characters are listed in Table 3.
|
84
|
+
// Table 3: Line Terminator Characters
|
85
|
+
// Code Unit Value Name Formal Name
|
86
|
+
// \u000A Line Feed <LF>
|
87
|
+
// \u000D Carriage Return <CR>
|
88
|
+
// \u2028 Line separator <LS>
|
89
|
+
// \u2029 Paragraph separator <PS>
|
90
|
+
// Only the characters in Table 3 are treated as line terminators. Other new line or line
|
91
|
+
// breaking characters are treated as white space but not as line terminators.
|
92
|
+
return (
|
93
|
+
ch === 10 /* lineFeed */ ||
|
94
|
+
ch === 13 /* carriageReturn */ ||
|
95
|
+
ch === 8232 /* lineSeparator */ ||
|
96
|
+
ch === 8233 /* paragraphSeparator */
|
97
|
+
);
|
98
|
+
}
|
99
|
+
|
100
|
+
module.exports = DocumentLines;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint-plugin-ember-template-lint",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.21.0",
|
4
4
|
"description": "Provide linting for ember template",
|
5
5
|
"keywords": [
|
6
6
|
"eslint",
|
@@ -10,20 +10,15 @@
|
|
10
10
|
"author": "Patrick Pircher",
|
11
11
|
"main": "lib/index.js",
|
12
12
|
"repository": "patricklx/eslint-plugin-ember-template-lint",
|
13
|
-
"scripts": {
|
14
|
-
"test": "jest",
|
15
|
-
"lint:js": "eslint --cache .",
|
16
|
-
"test:watch": "jest --watchAll"
|
17
|
-
},
|
18
13
|
"dependencies": {
|
19
|
-
"@glimmer/syntax": "^0.
|
20
|
-
"@typescript-eslint/parser": "^
|
21
|
-
"@typescript-eslint/typescript-estree": "^
|
14
|
+
"@glimmer/syntax": "^0.93.0",
|
15
|
+
"@typescript-eslint/parser": "^8.16.0",
|
16
|
+
"@typescript-eslint/typescript-estree": "^8.16.0",
|
22
17
|
"ember-eslint-parser": "^0.5.6",
|
23
18
|
"ember-template-recast": "^6.1.5",
|
24
19
|
"ember-template-lint": "^6.0.0",
|
25
20
|
"prettier-linter-helpers": "^1.0.0",
|
26
|
-
"synckit": "^0.
|
21
|
+
"synckit": "^0.9.2",
|
27
22
|
"typescript": "^5.0.4"
|
28
23
|
},
|
29
24
|
"peerDependencies": {
|
@@ -33,7 +28,7 @@
|
|
33
28
|
"ember-template-lint-plugin-prettier": "^5.0.0",
|
34
29
|
"eslint": "^8.41.0",
|
35
30
|
"eslint-plugin-jest": "^27.2.1",
|
36
|
-
"eslint-plugin-
|
31
|
+
"eslint-plugin-n": "^17.14.0",
|
37
32
|
"jest": "^29.5.0"
|
38
33
|
},
|
39
34
|
"jest": {
|
@@ -46,6 +41,13 @@
|
|
46
41
|
},
|
47
42
|
"license": "ISC",
|
48
43
|
"files": [
|
49
|
-
"lib
|
50
|
-
]
|
51
|
-
|
44
|
+
"lib/**/*.js"
|
45
|
+
],
|
46
|
+
"scripts": {
|
47
|
+
"test": "pnpm run /test:.*/",
|
48
|
+
"test:jest": "jest",
|
49
|
+
"lint:js": "eslint --cache lib tests",
|
50
|
+
"lint:js:fix": "eslint --cache lib tests --fix",
|
51
|
+
"test-watch": "jest --watchAll"
|
52
|
+
}
|
53
|
+
}
|