isml-linter 5.40.5 → 5.42.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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.42.0] - 2023-01-25
4
+
5
+ ### Added
6
+ - Introducing ["strict-void-elements" rule][strict-void-elements-readme];
7
+
8
+ ### Changed
9
+ - Allow Closing Tags for Void Elements in AST. "strict-void-elements" rule will optionally handle that invalid scenario;
10
+
11
+ ### Fixed
12
+ - "indent" Rule - Add "line" Attribute to Quote Issue Report;
13
+
14
+ ## [5.41.0] - 2023-01-21
15
+
16
+ ### Added
17
+ - Exposed "fix" method in [ISML Linter API][api-docs];
18
+
19
+ ### Fixed
20
+ - Line-by-line and tree rules issues now are fixed in a single ISML Linter execution, not in two steps anymore;
21
+
3
22
  ## [5.40.5] - 2023-01-15
4
23
 
5
24
  ### Fixed
@@ -1040,6 +1059,8 @@
1040
1059
  ### Added
1041
1060
  - Linter is published;
1042
1061
 
1062
+ [5.42.0]: https://github.com/FabiowQuixada/isml-linter/compare/v5.41.0...v5.42.0
1063
+ [5.41.0]: https://github.com/FabiowQuixada/isml-linter/compare/v5.40.5...v5.41.0
1043
1064
  [5.40.5]: https://github.com/FabiowQuixada/isml-linter/compare/v5.40.4...v5.40.5
1044
1065
  [5.40.4]: https://github.com/FabiowQuixada/isml-linter/compare/v5.40.3...v5.40.4
1045
1066
  [5.40.3]: https://github.com/FabiowQuixada/isml-linter/compare/v5.40.2...v5.40.3
@@ -1202,11 +1223,12 @@
1202
1223
  [api-docs]: <docs/api.md>
1203
1224
  [license]: <LICENSE>
1204
1225
 
1205
- [disallow-tags-readme]: <docs/rules/disallow-tags.md>
1206
- [no-br-readme]: <docs/rules/no-br.md>
1207
- [no-inline-style-readme]: <docs/rules/no-br.md>
1208
- [no-isscript-readme]: <docs/rules/no-isscript.md>
1209
- [enforce-security-readme]: <docs/rules/enforce-security.md>
1210
- [no-hardcode-readme]: <docs/rules/no-hardcode.md>
1211
- [indent-readme]: <docs/rules/indent.md>
1212
- [eslint-to-isscript-readme]: <docs/rules/eslint-to-isscript.md>
1226
+ [strict-void-elements-readme]: <docs/rules/strict-void-elements.md>
1227
+ [disallow-tags-readme]: <docs/rules/disallow-tags.md>
1228
+ [no-br-readme]: <docs/rules/no-br.md>
1229
+ [no-inline-style-readme]: <docs/rules/no-br.md>
1230
+ [no-isscript-readme]: <docs/rules/no-isscript.md>
1231
+ [enforce-security-readme]: <docs/rules/enforce-security.md>
1232
+ [no-hardcode-readme]: <docs/rules/no-hardcode.md>
1233
+ [indent-readme]: <docs/rules/indent.md>
1234
+ [eslint-to-isscript-readme]: <docs/rules/eslint-to-isscript.md>
package/README.md CHANGED
@@ -3,7 +3,7 @@ ISML Linter is a tool for examining if your project's templates follow a specifi
3
3
 
4
4
  - Styles that are defined by your team;
5
5
  - Syntactic errors related to `<is* >` tags;
6
- - Coding conventions recommended by SalesForce;
6
+ - Coding conventions recommended by Salesforce;
7
7
  - Git conflicts that may accidentally be left unresolved;
8
8
 
9
9
  Please feel free to make suggestions and help make this linter better. :) The set of currently available rules can be found below.
@@ -161,7 +161,7 @@ Please check the [Generic Configurations for Rules][generic-rule-config] page.
161
161
  | ------------------------------------------------------------------------------------ |:-----------------------------------------|
162
162
  | :exclamation: [no-br][no-br-readme] | <span style="color:orange">[Deprecated]</span> Disallows `<br/>` tags. Enable this rule if you prefer to use CSS to handle vertical spacing |
163
163
  | [no-git-conflict][no-git-conflict-readme] | Disallows unresolved Git conflicts |
164
- | [no-import-package][no-import-package-readme] | Disallows `importPackage()` function. It is recommended by SalesForce to use `require()` instead |
164
+ | [no-import-package][no-import-package-readme] | Disallows `importPackage()` function. It is recommended by Salesforce to use `require()` instead |
165
165
  | :exclamation: [no-isscript][no-isscript-readme] | <span style="color:orange">[Deprecated]</span> Disallows `<isscript/>` tag in template. Enable this rule if you prefer logic to be kept in a separate .ds/.js file |
166
166
  | :wrench: [no-trailing-spaces][no-trailing-spaces-readme] | Disallows trailing blank spaces |
167
167
  | :wrench: [no-space-only-lines][no-space-only-lines-readme] | Disallows lines that contain only blank spaces, i.e., unnecessarily indented |
@@ -189,6 +189,7 @@ Please check the [Generic Configurations for Rules][generic-rule-config] page.
189
189
  | :small_orange_diamond: [align-isset][align-isset-readme] | Aligns contiguous `<isset>` tags attributes' columns |
190
190
  | :small_orange_diamond: [enforce-security][enforce-security-readme] | Enforces security measures |
191
191
  | :wrench: :small_orange_diamond: [no-redundant-context][no-redundant-context-readme] | Prevents use of unnecessary contexts, such as `dw.web.Resource` |
192
+ | :small_orange_diamond: [strict-void-elements][strict-void-elements-readme] | Disallows closing tags for void elements, such as `<input>` and `<img>` |
192
193
 
193
194
  You are more than welcome to contribute with us! Please check the [contribute section][contribute-docs].
194
195
 
@@ -235,6 +236,7 @@ This project was conceived by its author without any financial support, with the
235
236
  [disallow-tags-readme]: <docs/rules/disallow-tags.md>
236
237
  [enforce-security-readme]: <docs/rules/enforce-security.md>
237
238
  [no-redundant-context-readme]: <docs/rules/no-redundant-context.md>
239
+ [strict-void-elements-readme]: <docs/rules/strict-void-elements.md>
238
240
 
239
241
  [api-docs]: <docs/api.md>
240
242
  [cli-docs]: <docs/cli.md>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isml-linter",
3
- "version": "5.40.5",
3
+ "version": "5.42.0",
4
4
  "author": "Fabiow Quixadá <ftquixada@gmail.com>",
5
5
  "license": "MIT",
6
6
  "main": "src/publicApi.js",
@@ -38,6 +38,7 @@ const config = {
38
38
  values : ['isscript', 'br', 'style', 'iframe']
39
39
  },
40
40
  'enforce-security' : {},
41
+ 'strict-void-elements' : {},
41
42
 
42
43
  // Other
43
44
  'lowercase-filename' : {}
package/src/IsmlLinter.js CHANGED
@@ -101,12 +101,12 @@ const getEmptyResult = () => {
101
101
  };
102
102
  };
103
103
 
104
- const checkTemplate = (templatePath, data, content, templateName) => {
104
+ const parseAndPossiblyFixTemplate = (templatePath, data, content, templateName) => {
105
105
  const formattedTemplatePath = GeneralUtils.formatTemplatePath(templatePath);
106
106
  const templateResults = getEmptyResult();
107
107
 
108
108
  try {
109
- const parseResult = RuleUtils.checkTemplate(templatePath, data, content, templateName);
109
+ const parseResult = RuleUtils.parseAndPossiblyFixTemplate(templatePath, data, content, templateName);
110
110
 
111
111
  if (parseResult.fixed) {
112
112
  templateResults.templatesFixed++;
@@ -232,7 +232,7 @@ Linter.run = (pathData, content, data = {}) => {
232
232
  const isIgnored = FileUtils.isIgnored(templatePath);
233
233
 
234
234
  if (!isIgnored) {
235
- const templateResults = checkTemplate(templatePath, data, content, templateName);
235
+ const templateResults = parseAndPossiblyFixTemplate(templatePath, data, content, templateName);
236
236
 
237
237
  finalResult = merge(finalResult, templateResults);
238
238
  }
@@ -149,6 +149,7 @@ class IsmlNode {
149
149
  newNode.isEmbeddedNode = this.isEmbeddedNode;
150
150
  }
151
151
 
152
+ getChild(number) { return this.children[number]; }
152
153
  getLastChild() { return this.children[this.children.length - 1]; }
153
154
  getChildrenQty() { return this.children.length; }
154
155
  hasChildren() { return this.children.length > 0; }
@@ -136,7 +136,7 @@ const parseNextElement = state => {
136
136
  }
137
137
 
138
138
  state.elementList.push(newElement);
139
-
139
+
140
140
  if (newElement.type === 'htmlTag' && newElement.value.indexOf('<isif') >= 0 && newElement.value.indexOf('</isif') < 0) {
141
141
  throw ExceptionUtils.invalidNestedIsifError(
142
142
  newElement.tagType,
@@ -110,14 +110,8 @@ const parseContainerElements = (element, currentParent, newNode, templatePath) =
110
110
 
111
111
  const parseNonContainerElements = (element, currentParent, newNode, templatePath) => {
112
112
  if (element.isSelfClosing) {
113
- if (element.isClosingTag && element.isVoidElement) {
114
- throw ExceptionUtils.voidElementClosingTag(
115
- element.tagType,
116
- element.lineNumber,
117
- element.globalPos,
118
- element.value.trim().length,
119
- templatePath
120
- );
113
+ if (element.isClosingTag && element.isVoidElement) {
114
+ currentParent.getLastChild().setTail(element.value, element.lineNumber, element.columnNumber, element.globalPos);
121
115
  } else {
122
116
  currentParent.addChild(newNode);
123
117
  }
package/src/publicApi.js CHANGED
@@ -14,8 +14,23 @@ module.exports = {
14
14
  }
15
15
 
16
16
  linterResult = IsmlLinter.run(path, content);
17
+
17
18
  return linterResult;
18
19
  },
20
+ fix : (path, content, config) => {
21
+ let autofixConfig = config;
22
+
23
+ if (config) {
24
+ autofixConfig.autoFix = true;
25
+ IsmlLinter.setConfig(autofixConfig);
26
+ } else {
27
+ autofixConfig = IsmlLinter.getConfig();
28
+ autofixConfig.autoFix = true;
29
+ IsmlLinter.setConfig(autofixConfig);
30
+ }
31
+
32
+ return IsmlLinter.run(path, content, autofixConfig);
33
+ },
19
34
  printResults : () => ConsoleUtils.displayOccurrenceList(linterResult),
20
35
  build : path => Builder.run(path),
21
36
 
@@ -41,7 +41,10 @@ SingleLineRulePrototype.check = function(templateContent, data = { isCrlfLineBre
41
41
  };
42
42
  }
43
43
 
44
- return { occurrenceList };
44
+ return {
45
+ occurrenceList,
46
+ fixedContent : templateContent
47
+ };
45
48
  };
46
49
 
47
50
  module.exports = SingleLineRulePrototype;
@@ -161,8 +161,14 @@ Rule.isQuoteClosingCharBroken = function(node) {
161
161
  '';
162
162
 
163
163
  if (message) {
164
+ const line = node
165
+ .getRoot()
166
+ .toString()
167
+ .split(Constants.EOL)[lineNumber - 1];
168
+
164
169
  result.push({
165
170
  quoteChar : attribute.quoteChar,
171
+ line : line,
166
172
  lineNumber : lineNumber,
167
173
  columnNumber : columnNumber,
168
174
  globalPos : globalPos,
@@ -0,0 +1,46 @@
1
+ const TreeRulePrototype = require('../prototypes/TreeRulePrototype');
2
+
3
+ const ruleId = require('path').basename(__filename).slice(0, -3);
4
+ const description = 'This is a void element, and as such, should not have a corresponding closing tag';
5
+
6
+ const Rule = Object.create(TreeRulePrototype);
7
+
8
+ Rule.init(ruleId, description);
9
+
10
+ Rule.isBroken = function(node) {
11
+ return node.isVoidElement() && node.tail.length > 0;
12
+ };
13
+
14
+ Rule.check = function(node, data) {
15
+
16
+ const ruleConfig = this.getConfigs();
17
+ let occurrenceList = [];
18
+
19
+ occurrenceList = this.checkChildren(node, data);
20
+
21
+ if (this.isBroken(node)) {
22
+ const error = this.getError(
23
+ node.tail.trim(),
24
+ node.tailLineNumber,
25
+ node.tailColumnNumber,
26
+ node.tailGlobalPos,
27
+ node.tail.trim().length,
28
+ `"<${node.getType()}>" is a void element, and as such, should not have a corresponding closing tag`
29
+ );
30
+
31
+ occurrenceList.push(error);
32
+ }
33
+
34
+ return this.return(node, occurrenceList, ruleConfig);
35
+ };
36
+
37
+ Rule.getFixedContent = node => {
38
+
39
+ if (node.isVoidElement()) {
40
+ node.setTail('', null, null, null);
41
+ }
42
+
43
+ return node.toString();
44
+ };
45
+
46
+ module.exports = Rule;
@@ -158,7 +158,7 @@ const existEslintConfigFile = () => {
158
158
  FileUtils.fileExists(Constants.eslintConfigFilePathList[2]);
159
159
  };
160
160
 
161
- const isTestEnv = () => process.env.NODE_ENV === Constants.ENV_TEST;
161
+ const isTestEnv = () => process.env.NODE_ENV === Constants.ENV_TEST && !global.isSimulatingProductionEnvironment;
162
162
 
163
163
  const setLocalConfig = configParam => {
164
164
  if (isTestEnv()) {
@@ -64,7 +64,7 @@ const checkCustomTag = tag => {
64
64
  }
65
65
  };
66
66
 
67
- const applyRuleResult = (config, ruleResult, templatePath, templateResults, rule) => {
67
+ const fixTemplateOrReportIssues = (config, ruleResult, templatePath, templateResults, rule) => {
68
68
  if (config.autoFix && ruleResult.fixedContent) {
69
69
  fs.writeFileSync(templatePath, ruleResult.fixedContent);
70
70
  templateResults.fixed = true;
@@ -78,7 +78,7 @@ const applyRuleResult = (config, ruleResult, templatePath, templateResults, rule
78
78
  }
79
79
  };
80
80
 
81
- const applyRuleOnTemplate = (ruleArray, templatePath, root, config, data) => {
81
+ const fixTemplateOrReportIssuesForRuleList = (ruleArray, templatePath, root, config, data) => {
82
82
  const templateResults = {
83
83
  fixed : false,
84
84
  errors : {},
@@ -92,8 +92,9 @@ const applyRuleOnTemplate = (ruleArray, templatePath, root, config, data) => {
92
92
  if (!rule.shouldIgnore(templatePath)) {
93
93
  try {
94
94
  ConsoleUtils.displayVerboseMessage(`Applying "${rule.id}" rule`, 1);
95
- const ruleResults = rule.check(root, templateResults.data);
96
- applyRuleResult(config, ruleResults, templatePath, templateResults, rule);
95
+ const ruleResults = rule.check(root, templateResults.data);
96
+ templateResults.finalContent = ruleResults.fixedContent;
97
+ fixTemplateOrReportIssues(config, ruleResults, templatePath, templateResults, rule);
97
98
 
98
99
  } catch (error) {
99
100
  throw ExceptionUtils.ruleApplianceError(rule, error, templatePath);
@@ -111,10 +112,10 @@ const findNodeOfType = (node, type) => {
111
112
  if (child.isOfType(type)) {
112
113
  result = child;
113
114
  return true;
114
- } else {
115
- result = findNodeOfType(child, type) || result;
116
115
  }
117
116
 
117
+ result = findNodeOfType(child, type) || result;
118
+
118
119
  return false;
119
120
  });
120
121
 
@@ -169,7 +170,7 @@ const checkFileName = (filename, templateContent) => {
169
170
  return templateResults;
170
171
  };
171
172
 
172
- const checkTreeRules = (templatePath, templateContent, config, data) => {
173
+ const checkAndPossiblyFixTreeRules = (templatePath, templateContent, config, data) => {
173
174
  if (!config.disableTreeParse) {
174
175
  ConsoleUtils.displayVerboseMessage(`Building tree for "${templatePath}"`, 1);
175
176
  const tree = TreeBuilder.build(templatePath, templateContent);
@@ -180,7 +181,7 @@ const checkTreeRules = (templatePath, templateContent, config, data) => {
180
181
 
181
182
  const ruleArray = getEnabledTreeRules();
182
183
 
183
- return applyRuleOnTemplate(
184
+ return fixTemplateOrReportIssuesForRuleList(
184
185
  ruleArray,
185
186
  templatePath,
186
187
  tree.rootNode,
@@ -189,10 +190,10 @@ const checkTreeRules = (templatePath, templateContent, config, data) => {
189
190
  }
190
191
  };
191
192
 
192
- const checkLineByLineRules = (templatePath, templateContent, config, data) => {
193
+ const checkAndPossiblyFixLineByLineRules = (templatePath, templateContent, config, data) => {
193
194
  const ruleArray = getEnabledLineRules();
194
195
 
195
- return applyRuleOnTemplate(
196
+ return fixTemplateOrReportIssuesForRuleList(
196
197
  ruleArray,
197
198
  templatePath,
198
199
  templateContent,
@@ -221,12 +222,12 @@ const checkCustomModules = () => {
221
222
  return moduleResults;
222
223
  };
223
224
 
224
- const checkTemplate = (templatePath, data, content = '', templateName = '') => {
225
+ const parseAndPossiblyFixTemplate = (templatePath, data, content = '', templateName = '') => {
225
226
  ConsoleUtils.displayVerboseMessage(`\nChecking "${templatePath}" template`);
226
227
  const config = ConfigUtils.load();
227
228
  const templateContent = GeneralUtils.toLF(content || fs.readFileSync(templatePath, 'utf-8'));
228
- const lineResults = checkLineByLineRules(templatePath, templateContent, config, data);
229
- const treeResults = checkTreeRules(templatePath, templateContent, config, data) || { errors : [] };
229
+ const lineResults = checkAndPossiblyFixLineByLineRules(templatePath, templateContent, config, data);
230
+ const treeResults = checkAndPossiblyFixTreeRules(templatePath, lineResults.finalContent, config, data) || { errors : [] };
230
231
  const filenameResults = checkFileName(templateName, templateContent);
231
232
 
232
233
  return {
@@ -289,7 +290,7 @@ const getEnabledTreeRules = () => {
289
290
  module.exports.getAllLineRules = () => lineByLineRules;
290
291
  module.exports.findNodeOfType = findNodeOfType;
291
292
  module.exports.isTypeAmongTheFirstElements = isTypeAmongTheFirstElements;
292
- module.exports.checkTemplate = checkTemplate;
293
+ module.exports.parseAndPossiblyFixTemplate = parseAndPossiblyFixTemplate;
293
294
  module.exports.checkCustomModules = checkCustomModules;
294
295
  module.exports.getAvailableRulesQty = getAvailableRulesQty;
295
296
  module.exports.getLevelGroup = getLevelGroup;
@@ -6,7 +6,6 @@
6
6
  ===========================================================================
7
7
  **/
8
8
 
9
- const path = require('path');
10
9
  const fs = require('fs');
11
10
  const Constants = require('../Constants');
12
11
  const TreeBuilder = require('../isml_tree/TreeBuilder');
@@ -38,7 +37,7 @@ const checkCustomTag = tag => {
38
37
  }
39
38
  };
40
39
 
41
- const applyRuleResult = (config, ruleResult, templatePath, templateResults, rule) => {
40
+ const fixTemplateOrReportIssues = (config, ruleResult, templatePath, templateResults, rule) => {
42
41
  if (config.autoFix && ruleResult.fixedContent) {
43
42
  fs.writeFileSync(templatePath, ruleResult.fixedContent);
44
43
  templateResults.fixed = true;
@@ -49,7 +48,7 @@ const applyRuleResult = (config, ruleResult, templatePath, templateResults, rule
49
48
  }
50
49
  };
51
50
 
52
- const applyRuleOnTemplate = (ruleArray, templatePath, root, config) => {
51
+ const fixTemplateOrReportIssuesForRuleList = (ruleArray, templatePath, root, config) => {
53
52
  const templateResults = {
54
53
  fixed : false,
55
54
  errors : {}
@@ -59,7 +58,7 @@ const applyRuleOnTemplate = (ruleArray, templatePath, root, config) => {
59
58
  const rule = ruleArray[i];
60
59
  if (!rule.shouldIgnore(templatePath)) {
61
60
  const ruleResults = rule.check(root, templateResults.data);
62
- applyRuleResult(config, ruleResults, templatePath, templateResults, rule);
61
+ fixTemplateOrReportIssues(config, ruleResults, templatePath, templateResults, rule);
63
62
  }
64
63
  }
65
64
 
@@ -130,7 +129,7 @@ const checkFileName = (filename, templateContent) => {
130
129
  return templateResults;
131
130
  };
132
131
 
133
- const checkTreeRules = (templatePath, templateContent, config) => {
132
+ const checkAndPossiblyFixTreeRules = (templatePath, templateContent, config) => {
134
133
  if (!config.disableTreeParse) {
135
134
  const tree = TreeBuilder.build(templatePath, templateContent);
136
135
 
@@ -140,7 +139,7 @@ const checkTreeRules = (templatePath, templateContent, config) => {
140
139
 
141
140
  const ruleArray = getEnabledTreeRules();
142
141
 
143
- return applyRuleOnTemplate(
142
+ return fixTemplateOrReportIssuesForRuleList(
144
143
  ruleArray,
145
144
  templatePath,
146
145
  tree.rootNode,
@@ -148,10 +147,10 @@ const checkTreeRules = (templatePath, templateContent, config) => {
148
147
  }
149
148
  };
150
149
 
151
- const checkLineByLineRules = (templatePath, templateContent, config) => {
150
+ const checkAndPossiblyFixLineByLineRules = (templatePath, templateContent, config) => {
152
151
  const ruleArray = getEnabledLineRules();
153
152
 
154
- return applyRuleOnTemplate(
153
+ return fixTemplateOrReportIssuesForRuleList(
155
154
  ruleArray,
156
155
  templatePath,
157
156
  templateContent,
@@ -176,11 +175,11 @@ const checkCustomModules = () => {
176
175
  return moduleResults;
177
176
  };
178
177
 
179
- const checkTemplate = (templatePath, content, templateName) => {
178
+ const parseAndPossiblyFixTemplate = (templatePath, content, templateName) => {
180
179
  const config = ConfigUtils.load();
181
180
  const templateContent = content || fs.readFileSync(templatePath, 'utf-8');
182
- const lineResults = checkLineByLineRules(templatePath, templateContent, config);
183
- const treeResults = checkTreeRules(templatePath, templateContent, config) || { errors : [] };
181
+ const lineResults = checkAndPossiblyFixLineByLineRules(templatePath, templateContent, config);
182
+ const treeResults = checkAndPossiblyFixTreeRules(templatePath, templateContent, config) || { errors : [] };
184
183
  const filenameResults = checkFileName(templateName, templateContent);
185
184
 
186
185
  return {
@@ -227,6 +226,6 @@ const getEnabledTreeRules = () => {
227
226
  module.exports.getAllLineRules = () => lineByLineRules;
228
227
  module.exports.findNodeOfType = findNodeOfType;
229
228
  module.exports.isTypeAmongTheFirstElements = isTypeAmongTheFirstElements;
230
- module.exports.checkTemplate = checkTemplate;
229
+ module.exports.parseAndPossiblyFixTemplate = parseAndPossiblyFixTemplate;
231
230
  module.exports.checkCustomModules = checkCustomModules;
232
231
  module.exports.getAvailableRulesQty = getAvailableRulesQty;