gscan 4.37.1 → 4.37.3
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/app/uploads/6caebb4b217a1ac54be19902abae7cbb +0 -0
- package/lib/ast-linter/helpers/index.js +3 -0
- package/lib/ast-linter/linter.js +7 -1
- package/lib/ast-linter/rules/base.js +6 -0
- package/lib/ast-linter/rules/index.js +2 -1
- package/lib/ast-linter/rules/internal/scope.js +38 -24
- package/lib/ast-linter/rules/lint-no-unknown-globals.js +19 -3
- package/lib/ast-linter/rules/lint-no-unknown-page-properties.js +21 -0
- package/lib/ast-linter/rules/mark-used-page-properties.js +15 -0
- package/lib/checks/090-template-syntax.js +1 -0
- package/lib/checks/110-page-builder-usage.js +185 -0
- package/lib/checks/120-no-unknown-globals.js +136 -0
- package/lib/specs/canary.js +7 -0
- package/lib/specs/v1.js +1 -1
- package/package.json +4 -4
|
Binary file
|
package/lib/ast-linter/linter.js
CHANGED
|
@@ -20,6 +20,7 @@ class Linter {
|
|
|
20
20
|
this.helpers = [];
|
|
21
21
|
this.inlinePartials = [];
|
|
22
22
|
this.customThemeSettings = [];
|
|
23
|
+
this.usedPageProperties = [];
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -57,7 +58,8 @@ class Linter {
|
|
|
57
58
|
partials: [],
|
|
58
59
|
helpers: [],
|
|
59
60
|
inlinePartials: [],
|
|
60
|
-
customThemeSettings: []
|
|
61
|
+
customThemeSettings: [],
|
|
62
|
+
usedPageProperties: []
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
Scanner.prototype = new Handlebars.Visitor();
|
|
@@ -161,6 +163,10 @@ class Linter {
|
|
|
161
163
|
this.inlinePartials = scanner.context.inlinePartials;
|
|
162
164
|
}
|
|
163
165
|
|
|
166
|
+
if (scanner.context.usedPageProperties) {
|
|
167
|
+
this.usedPageProperties = scanner.context.usedPageProperties;
|
|
168
|
+
}
|
|
169
|
+
|
|
164
170
|
return messages;
|
|
165
171
|
}
|
|
166
172
|
}
|
|
@@ -9,6 +9,8 @@ module.exports = class BaseRule {
|
|
|
9
9
|
this.helpers = options.helpers;
|
|
10
10
|
this.inlinePartials = options.inlinePartials || [];
|
|
11
11
|
this.customThemeSettings = options.customThemeSettings;
|
|
12
|
+
// TODO: remove hardcoded list of known page builder properties once we have a way to get them from the spec
|
|
13
|
+
this.knownPageBuilderProperties = options.knownPageBuilderProperties || ['show_title_and_feature_image'];
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
getVisitor({fileName} = {}) {
|
|
@@ -160,6 +162,10 @@ module.exports = class BaseRule {
|
|
|
160
162
|
return this.helpers && this.helpers.includes(nodeName);
|
|
161
163
|
}
|
|
162
164
|
|
|
165
|
+
isValidPageBuilderProperty(property) {
|
|
166
|
+
return this.knownPageBuilderProperties && this.knownPageBuilderProperties.includes(property);
|
|
167
|
+
}
|
|
168
|
+
|
|
163
169
|
isValidCustomThemeSettingReference(name) {
|
|
164
170
|
return this.customThemeSettings && !!this.customThemeSettings[name];
|
|
165
171
|
}
|
|
@@ -12,10 +12,11 @@ module.exports = {
|
|
|
12
12
|
'GS090-NO-PRICE-DATA-CURRENCY-GLOBAL': require('./lint-no-price-data-currency-global'),
|
|
13
13
|
'GS090-NO-PRICE-DATA-CURRENCY-CONTEXT': require('./lint-no-price-data-currency-context'),
|
|
14
14
|
'GS090-NO-PRICE-DATA-MONTHLY-YEARLY': require('./lint-no-price-data-monthly-yearly'),
|
|
15
|
+
'GS110-NO-UNKNOWN-PAGE-BUILDER-USAGE': require('./lint-no-unknown-page-properties'),
|
|
16
|
+
'GS120-NO-UNKNOWN-GLOBALS': require('./lint-no-unknown-globals'),
|
|
15
17
|
'no-multi-param-conditionals': require('./lint-no-multi-param-conditionals'),
|
|
16
18
|
'no-nested-async-helpers': require('./lint-no-nested-async-helpers'),
|
|
17
19
|
'no-prev-next-post-outside-post-context': require('./lint-no-prev-next-post-outside-post-context'),
|
|
18
|
-
'no-unknown-globals': require('./lint-no-unknown-globals'),
|
|
19
20
|
'no-unknown-partials': require('./lint-no-unknown-partials'),
|
|
20
21
|
'no-unknown-helpers': require('./lint-no-unknown-helpers')
|
|
21
22
|
};
|
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
const {getNodeName} = require('../../helpers');
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
4
|
+
// TODO: the allowlist should include all properties of these top-level globals
|
|
5
|
+
const ghostGlobals = {
|
|
6
|
+
site: true,
|
|
7
|
+
member: true,
|
|
8
|
+
setting: true, // TODO: we should remove this but the Journal theme is using it atm
|
|
9
|
+
config: true,
|
|
10
|
+
labs: true,
|
|
11
|
+
custom: true
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// unless we update AST to check that we're within a foreach block, we need to allowlist all of these as they look the same as globals
|
|
15
|
+
const dataVars = {
|
|
16
|
+
index: true,
|
|
17
|
+
number: true,
|
|
18
|
+
key: true,
|
|
19
|
+
first: true,
|
|
20
|
+
last: true,
|
|
21
|
+
odd: true,
|
|
22
|
+
even: true,
|
|
23
|
+
rowStart: true,
|
|
24
|
+
rowEnd: true
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// unless we move from lodash to a glob match, we just need to handle all custom since we can't allowlist those
|
|
28
|
+
function isOnAllowlist(parts) {
|
|
29
|
+
const variable = parts && parts[0];
|
|
30
|
+
return ghostGlobals[variable] || dataVars[variable] || false;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
// true = property exists
|
|
28
34
|
// 'context' = has context's shape
|
|
29
35
|
// ['context] = array of context's shape
|
|
@@ -218,10 +224,18 @@ class Scope {
|
|
|
218
224
|
return matchedFrame && matchedFrame.node;
|
|
219
225
|
}
|
|
220
226
|
|
|
221
|
-
|
|
222
|
-
// @foo
|
|
227
|
+
isKnownVariable(node) {
|
|
228
|
+
// @foo statements are referencing globals rather than locals
|
|
229
|
+
// and can be detected with the data: true attribute (???)
|
|
230
|
+
|
|
231
|
+
// they can be direct [Mustache] statements {{@foo}}...
|
|
223
232
|
if (node.type === 'MustacheStatement' && node.path.data) {
|
|
224
|
-
return
|
|
233
|
+
return isOnAllowlist(node.path.parts);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ... or indirect using helpers, e.g. {{#match @foo.bar}}{{/match}}
|
|
237
|
+
if (node.type === 'PathExpression') {
|
|
238
|
+
return isOnAllowlist(node.parts);
|
|
225
239
|
}
|
|
226
240
|
|
|
227
241
|
let name = getNodeName(node);
|
|
@@ -2,8 +2,8 @@ const Rule = require('./base');
|
|
|
2
2
|
const {logNode} = require('../helpers');
|
|
3
3
|
|
|
4
4
|
module.exports = class NoUnknownGlobals extends Rule {
|
|
5
|
-
|
|
6
|
-
if (node.path.data && !this.scope.
|
|
5
|
+
_checkMustacheForUnknownGlobal(node) {
|
|
6
|
+
if (node.path.data && !this.scope.isKnownVariable(node)) {
|
|
7
7
|
this.log({
|
|
8
8
|
message: `${logNode(node)} is not a known global`,
|
|
9
9
|
line: node.loc && node.loc.start.line,
|
|
@@ -13,9 +13,25 @@ module.exports = class NoUnknownGlobals extends Rule {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
_checkBlockForUnknownGlobal(node) {
|
|
17
|
+
if (node.path.type === 'PathExpression') {
|
|
18
|
+
node.params.forEach((param) => {
|
|
19
|
+
if (param.data && !this.scope.isKnownVariable(param)) {
|
|
20
|
+
this.log({
|
|
21
|
+
message: `${logNode(param)} is not a known global`,
|
|
22
|
+
line: param.loc && param.loc.start.line,
|
|
23
|
+
column: param.loc && param.loc.start.column,
|
|
24
|
+
source: this.sourceForNode(param)
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
visitor() {
|
|
17
32
|
return {
|
|
18
|
-
MustacheStatement: this.
|
|
33
|
+
MustacheStatement: this._checkMustacheForUnknownGlobal.bind(this),
|
|
34
|
+
BlockStatement: this._checkBlockForUnknownGlobal.bind(this)
|
|
19
35
|
};
|
|
20
36
|
}
|
|
21
37
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const Rule = require('./base');
|
|
2
|
+
const {logNode} = require('../helpers');
|
|
3
|
+
|
|
4
|
+
module.exports = class NoUnknownPageProperties extends Rule {
|
|
5
|
+
_checkForUnknownPageProperty(node) {
|
|
6
|
+
if (node.data && node.parts && node.parts[0] === 'page' && (node.parts.length > 2 || !this.isValidPageBuilderProperty(node.parts[1]))) {
|
|
7
|
+
this.log({
|
|
8
|
+
message: `${logNode(node)} is not a known @page property`,
|
|
9
|
+
line: node.loc && node.loc.start.line,
|
|
10
|
+
column: node.loc && node.loc.start.column,
|
|
11
|
+
source: this.sourceForNode(node)
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
visitor() {
|
|
17
|
+
return {
|
|
18
|
+
PathExpression: this._checkForUnknownPageProperty.bind(this)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const Rule = require('./base');
|
|
2
|
+
|
|
3
|
+
module.exports = class MarkUsedPageProperties extends Rule {
|
|
4
|
+
_markUsedPageProperties(node) {
|
|
5
|
+
if (node.data && node.parts && node.parts.length === 2 && node.parts[0] === 'page') {
|
|
6
|
+
this.scanner.context.usedPageProperties.push(node.parts[1]);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
visitor() {
|
|
11
|
+
return {
|
|
12
|
+
PathExpression: this._markUsedPageProperties.bind(this)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const spec = require('../specs');
|
|
3
|
+
const {versions, normalizePath} = require('../utils');
|
|
4
|
+
const ASTLinter = require('../ast-linter');
|
|
5
|
+
|
|
6
|
+
function getRules(id, options) {
|
|
7
|
+
const checkVersion = _.get(options, 'checkVersion', versions.default);
|
|
8
|
+
let ruleSet = spec.get([checkVersion]);
|
|
9
|
+
|
|
10
|
+
const ruleRegex = new RegExp('^' + id + '-.*', 'g');
|
|
11
|
+
ruleSet = _.pickBy(ruleSet.rules, function (rule, ruleCode) {
|
|
12
|
+
if (ruleCode.match(ruleRegex)) {
|
|
13
|
+
return rule;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return ruleSet;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getLogger({theme, rule, file = null}) {
|
|
21
|
+
return {
|
|
22
|
+
failure: (content) => {
|
|
23
|
+
if (!theme.results.fail[rule.code]) {
|
|
24
|
+
theme.results.fail[rule.code] = {failures: []};
|
|
25
|
+
}
|
|
26
|
+
const failure = {
|
|
27
|
+
...content,
|
|
28
|
+
rule: rule.code
|
|
29
|
+
};
|
|
30
|
+
if (file) {
|
|
31
|
+
failure.ref = file.file;
|
|
32
|
+
}
|
|
33
|
+
theme.results.fail[rule.code].failures.push(failure);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function applyRule(rule, theme) {
|
|
39
|
+
// The result variable is passed around to keep a state through the full lifecycle
|
|
40
|
+
const result = {};
|
|
41
|
+
try {
|
|
42
|
+
// Check if the rule is enabled (optional)
|
|
43
|
+
if (typeof rule.isEnabled === 'function') {
|
|
44
|
+
if (!rule.isEnabled({theme, log: getLogger({theme, rule}), result, options: rule.options})) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
} else if (typeof rule.isEnabled === 'boolean' && !rule.isEnabled) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Initialize the rule (optional)
|
|
52
|
+
if (typeof rule.init === 'function') {
|
|
53
|
+
rule.init({theme, log: getLogger({theme, rule}), result});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Run the main function on each theme file (optional)
|
|
57
|
+
if (typeof rule.eachFile === 'function') {
|
|
58
|
+
_.each(theme.files, function (themeFile) {
|
|
59
|
+
rule.eachFile({file: themeFile, theme, log: getLogger({theme, rule}), result});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Run the final function
|
|
64
|
+
if (typeof rule.done === 'function') {
|
|
65
|
+
rule.done({theme, log: getLogger({theme, rule}), result});
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
// Output something instead of failing silently (should never happen)
|
|
69
|
+
// eslint-disable-next-line
|
|
70
|
+
console.error('gscan failure', e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseWithAST({theme, log, file, rules, callback}) {
|
|
75
|
+
const linter = new ASTLinter();
|
|
76
|
+
|
|
77
|
+
// // This rule is needed to find partials
|
|
78
|
+
// // Partials are needed for a full parsing
|
|
79
|
+
if (!rules['mark-used-partials']) {
|
|
80
|
+
rules['mark-used-partials'] = require(`../ast-linter/rules/mark-used-partials`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function processFile(themeFile) {
|
|
84
|
+
if (themeFile.parsed.error) {
|
|
85
|
+
// Ignore parsing errors, they are handled in 005
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const astResults = linter.verify({
|
|
90
|
+
parsed: themeFile.parsed,
|
|
91
|
+
rules,
|
|
92
|
+
source: themeFile.content,
|
|
93
|
+
moduleId: themeFile.file
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (astResults.length) {
|
|
97
|
+
log.failure({
|
|
98
|
+
message: astResults[0].message
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (typeof callback === 'function') {
|
|
103
|
+
callback(linter);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
linter.partials.forEach(({node: partial}) => {
|
|
107
|
+
const partialFile = theme.files.find(f => normalizePath(f.file) === `partials/${normalizePath(partial)}.hbs`);
|
|
108
|
+
if (partialFile) {
|
|
109
|
+
processFile(partialFile);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return processFile(file);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ruleImplementations = {
|
|
118
|
+
'GS110-NO-MISSING-PAGE-BUILDER-USAGE': {
|
|
119
|
+
isEnabled: ({options}) => {
|
|
120
|
+
// TODO: change to `isEnabled: true` when removing labs flag
|
|
121
|
+
return options && options.labs && options.labs.pageImprovements;
|
|
122
|
+
},
|
|
123
|
+
init: ({result}) => {
|
|
124
|
+
result.pageBuilderProperties = new Set();
|
|
125
|
+
},
|
|
126
|
+
eachFile: ({file, theme, log, result}) => {
|
|
127
|
+
const templateTest = file.file.match(/(?<!partials\/.+?)\.hbs$/);
|
|
128
|
+
|
|
129
|
+
if (templateTest) {
|
|
130
|
+
parseWithAST({file, theme, rules: {
|
|
131
|
+
'mark-used-page-properties': require(`../ast-linter/rules/mark-used-page-properties`)
|
|
132
|
+
}, log, callback: (linter) => {
|
|
133
|
+
linter.usedPageProperties.forEach((variable) => {
|
|
134
|
+
result.pageBuilderProperties.add(variable);
|
|
135
|
+
});
|
|
136
|
+
}});
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
done: ({log, result}) => {
|
|
140
|
+
// TODO: get this from the spec rather than hard-coding to account for version changes
|
|
141
|
+
const knownPageBuilderProperties = ['show_title_and_feature_image'];
|
|
142
|
+
const notUsedProperties = knownPageBuilderProperties.filter(x => !result.pageBuilderProperties.has(x));
|
|
143
|
+
|
|
144
|
+
notUsedProperties.forEach((property) => {
|
|
145
|
+
log.failure({
|
|
146
|
+
ref: `@page.${property}`
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
'GS110-NO-UNKNOWN-PAGE-BUILDER-USAGE': {
|
|
152
|
+
isEnabled: ({options}) => {
|
|
153
|
+
// TODO: change to `isEnabled: true` when removing labs flag
|
|
154
|
+
return options && options.labs && options.labs.pageImprovements;
|
|
155
|
+
},
|
|
156
|
+
eachFile: ({file, theme, log}) => {
|
|
157
|
+
const templateTest = file.file.match(/(?<!partials\/.+?)\.hbs$/);
|
|
158
|
+
|
|
159
|
+
if (templateTest) {
|
|
160
|
+
parseWithAST({
|
|
161
|
+
file, theme, rules: {
|
|
162
|
+
'no-unknown-page-properties': require(`../ast-linter/rules/lint-no-unknown-page-properties`)
|
|
163
|
+
}, log, callback: () => {}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
function checkUsage(theme, options) {
|
|
171
|
+
const rules = getRules('GS110', options);
|
|
172
|
+
|
|
173
|
+
_.each(rules, function (check, ruleCode) {
|
|
174
|
+
applyRule({
|
|
175
|
+
code: ruleCode,
|
|
176
|
+
...check,
|
|
177
|
+
...ruleImplementations[ruleCode],
|
|
178
|
+
options
|
|
179
|
+
}, theme);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return theme;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = checkUsage;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const spec = require('../specs');
|
|
3
|
+
const versions = require('../utils').versions;
|
|
4
|
+
const ASTLinter = require('../ast-linter');
|
|
5
|
+
const {normalizePath} = require('../utils');
|
|
6
|
+
|
|
7
|
+
function processFileFunction(files, failures, theme, partialsFound) {
|
|
8
|
+
const processedFiles = [];
|
|
9
|
+
|
|
10
|
+
return function processFile(linter, themeFile, parentInlinePartials = []) {
|
|
11
|
+
if (processedFiles.includes(themeFile.file)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
processedFiles.push(themeFile.file);
|
|
16
|
+
|
|
17
|
+
// Reset inline partial variables
|
|
18
|
+
linter.inlinePartials = [];
|
|
19
|
+
linter.options.inlinePartials = [];
|
|
20
|
+
|
|
21
|
+
linter.verify({
|
|
22
|
+
parsed: themeFile.parsed,
|
|
23
|
+
rules: [
|
|
24
|
+
require('../ast-linter/rules/mark-declared-inline-partials')
|
|
25
|
+
],
|
|
26
|
+
source: themeFile.content,
|
|
27
|
+
moduleId: themeFile.file
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Store the inline partials for the actual partial linting
|
|
31
|
+
const inlinePartials = linter.inlinePartials;
|
|
32
|
+
linter.options.inlinePartials = [...inlinePartials, ...parentInlinePartials];
|
|
33
|
+
|
|
34
|
+
const astResults = linter.verify({
|
|
35
|
+
parsed: themeFile.parsed,
|
|
36
|
+
rules: [
|
|
37
|
+
require('../ast-linter/rules/mark-used-partials'),
|
|
38
|
+
require('../ast-linter/rules/lint-no-unknown-globals')
|
|
39
|
+
],
|
|
40
|
+
source: themeFile.content,
|
|
41
|
+
moduleId: themeFile.file
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (astResults.length) {
|
|
45
|
+
astResults.forEach((result) => {
|
|
46
|
+
failures.push({
|
|
47
|
+
ref: themeFile.file,
|
|
48
|
+
message: result.message
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
theme.helpers = theme.helpers || {};
|
|
54
|
+
linter.helpers.forEach((helper) => {
|
|
55
|
+
if (!theme.helpers[helper.name]) {
|
|
56
|
+
theme.helpers[helper.name] = [];
|
|
57
|
+
}
|
|
58
|
+
theme.helpers[helper.name].push(themeFile.file);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
linter.partials.forEach((partial) => {
|
|
62
|
+
const partialName = partial.node;
|
|
63
|
+
partialsFound[partialName] = true;
|
|
64
|
+
const file = files.find(f => normalizePath(f.file) === `partials/${normalizePath(partialName)}.hbs`);
|
|
65
|
+
if (file) {
|
|
66
|
+
// Find all inline partial declaration that were within the partial usage block
|
|
67
|
+
const childrenInlinePartials = [...parentInlinePartials];
|
|
68
|
+
for (const inline of inlinePartials) {
|
|
69
|
+
//Only partials that are in scope
|
|
70
|
+
if (inline.parents.some(node => node.type === partial.type &&
|
|
71
|
+
node.loc.source === partial.loc.source &&
|
|
72
|
+
node.loc.start.line === partial.loc.start.line &&
|
|
73
|
+
node.loc.start.column === partial.loc.start.column &&
|
|
74
|
+
node.loc.end.line === partial.loc.end.line &&
|
|
75
|
+
node.loc.end.column === partial.loc.end.column)) {
|
|
76
|
+
// Override the `parents` attribute as the inline partials are in another context than the children file
|
|
77
|
+
childrenInlinePartials.push({
|
|
78
|
+
...inline,
|
|
79
|
+
parents: []
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
processFile(linter, file, childrenInlinePartials);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const checkNoUnknownGlobals = function checkNoUnknownGlobals(theme, options) {
|
|
90
|
+
const failures = [];
|
|
91
|
+
const checkVersion = _.get(options, 'checkVersion', versions.default);
|
|
92
|
+
const ruleSet = spec.get([checkVersion]);
|
|
93
|
+
|
|
94
|
+
let partialsFound = {};
|
|
95
|
+
|
|
96
|
+
// Reset theme.helpers to make sure we only get helpers that are used
|
|
97
|
+
theme.helpers = {};
|
|
98
|
+
|
|
99
|
+
// CASE: 001-deprecations checks only needs `rules` that start with `GS001-DEPR-`
|
|
100
|
+
const ruleRegex = /GS120-.*/g;
|
|
101
|
+
|
|
102
|
+
const rulesToCheck = _.pickBy(ruleSet.rules, function (rule, ruleCode) {
|
|
103
|
+
if (ruleCode.match(ruleRegex)) {
|
|
104
|
+
return rule;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const processFile = processFileFunction(theme.files, failures, theme, partialsFound);
|
|
109
|
+
|
|
110
|
+
_.each(rulesToCheck, function (check, ruleCode) {
|
|
111
|
+
const linter = new ASTLinter({
|
|
112
|
+
partials: theme.partials,
|
|
113
|
+
helpers: ruleSet.knownHelpers
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
_.each(theme.files, function (themeFile) {
|
|
117
|
+
let templateTest = themeFile.file.match(/(?<!partials\/.+?)\.hbs$/);
|
|
118
|
+
|
|
119
|
+
if (templateTest) {
|
|
120
|
+
processFile(linter, themeFile);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
theme.partials = Object.keys(partialsFound);
|
|
125
|
+
|
|
126
|
+
if (failures.length > 0) {
|
|
127
|
+
theme.results.fail[ruleCode] = {failures: failures};
|
|
128
|
+
} else {
|
|
129
|
+
theme.results.pass.push(ruleCode);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return theme;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
module.exports = checkNoUnknownGlobals;
|
package/lib/specs/canary.js
CHANGED
|
@@ -712,6 +712,13 @@ let rules = {
|
|
|
712
712
|
rule: '<code>package.json</code> property <code>config.custom</code> contains an entry with a <code>description</code> that is too long',
|
|
713
713
|
details: oneLineTrim`<code>config.custom</code> entry <code>description</code> should be less than <code>100</code> characters so that it is displayed correctly.<br />
|
|
714
714
|
Check the <a href="${docsBaseUrl}custom-settings" target=_blank><code>config.custom</code> documentation</a> for further information.`
|
|
715
|
+
},
|
|
716
|
+
|
|
717
|
+
'GS120-NO-UNKNOWN-GLOBALS': {
|
|
718
|
+
level: 'error',
|
|
719
|
+
rule: 'Unknown global helper used',
|
|
720
|
+
details: oneLineTrim`A global helper was detected that is not supported by this version of Ghost. Check the
|
|
721
|
+
<a href="${docsBaseUrl}helpers/" target=_blank>helpers documentation</a> for further information.`
|
|
715
722
|
}
|
|
716
723
|
};
|
|
717
724
|
|
package/lib/specs/v1.js
CHANGED
|
@@ -387,7 +387,7 @@ rules = {
|
|
|
387
387
|
level: 'error',
|
|
388
388
|
rule: 'Templates must contain valid Handlebars',
|
|
389
389
|
fatal: true,
|
|
390
|
-
details: oneLineTrim`Oops! You seemed to have used invalid Handlebars syntax. This mostly happens
|
|
390
|
+
details: oneLineTrim`Oops! You seemed to have used invalid Handlebars syntax. This mostly happens when you use a helper that is not supported.<br>
|
|
391
391
|
See the full list of available helpers <a href="${docsBaseUrl}helpers/" target=_blank>here</a>.`
|
|
392
392
|
},
|
|
393
393
|
'GS010-PJ-REQ': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscan",
|
|
3
|
-
"version": "4.37.
|
|
3
|
+
"version": "4.37.3",
|
|
4
4
|
"description": "Scans Ghost themes looking for errors, deprecations, features and compatibility",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ghost",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"gscan": "./bin/cli.js"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@sentry/node": "7.
|
|
43
|
+
"@sentry/node": "7.60.1",
|
|
44
44
|
"@tryghost/config": "0.2.17",
|
|
45
45
|
"@tryghost/debug": "0.1.25",
|
|
46
46
|
"@tryghost/errors": "1.2.25",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"multer": "1.4.4",
|
|
60
60
|
"pluralize": "8.0.0",
|
|
61
61
|
"require-dir": "1.2.0",
|
|
62
|
-
"semver": "7.5.
|
|
62
|
+
"semver": "7.5.4",
|
|
63
63
|
"uuid": "9.0.0",
|
|
64
64
|
"validator": "13.0.0"
|
|
65
65
|
},
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"eslint-plugin-ghost": "2.1.0",
|
|
69
69
|
"istanbul": "0.4.5",
|
|
70
70
|
"mocha": "10.0.0",
|
|
71
|
-
"node-fetch": "3.3.
|
|
71
|
+
"node-fetch": "3.3.2",
|
|
72
72
|
"nodemon": "2.0.7",
|
|
73
73
|
"rewire": "6.0.0",
|
|
74
74
|
"should": "13.2.3",
|