Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.

Commit 8229ca7

Browse files
committed
Add support for VFELMemberExpressions and <apex:repeat> tags
1 parent 53c409d commit 8229ca7

File tree

9 files changed

+196
-98
lines changed

9 files changed

+196
-98
lines changed

dist/index.js

Lines changed: 100 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -867,8 +867,7 @@ function patchESLint() {
867867
var isVisualForce = visualForceExtensions.indexOf(extension) >= 0;
868868

869869
if (typeof textOrSourceCode === 'string' && isVisualForce) {
870-
var currentInfos = extract(textOrSourceCode, pluginSettings.indent, false // isXML
871-
);
870+
var currentInfos = extract(textOrSourceCode, pluginSettings.indent);
872871
// parsing the source code with the patched espree
873872
var espreePath = Object.keys(requireCache).find(function (key) {
874873
return key.endsWith(path.join('espree', 'espree.js'));
@@ -899,11 +898,12 @@ function patchESLint() {
899898
comment: true,
900899
filePath: filename
901900
});
901+
//console.log('code: ', String(currentInfos.code));
902902

903903
var ast = espree.parse(String(currentInfos.code), parserOptions);
904904
var sourceCode = new SourceCode(String(currentInfos.code), ast);
905905

906-
messages = remapMessages(localVerify(sourceCode), currentInfos.code, pluginSettings.reportBadIndent, currentInfos.badIndentationLines);
906+
messages = remapMessages(localVerify(sourceCode), currentInfos.code, pluginSettings.reportBadIndent, currentInfos.badIndentationLines, currentInfos.apexRepeatTags);
907907
sourceCodeForMessages.set(messages, textOrSourceCode);
908908
} else messages = localVerify(textOrSourceCode);
909909

@@ -921,7 +921,7 @@ function patchESLint() {
921921
};
922922
}
923923

924-
function remapMessages(messages, code, reportBadIndent, badIndentationLines) {
924+
function remapMessages(messages, code, reportBadIndent, badIndentationLines, apexRepeatTags) {
925925
var newMessages = [];
926926

927927
messages.forEach(function (message) {
@@ -960,6 +960,16 @@ function remapMessages(messages, code, reportBadIndent, badIndentationLines) {
960960
});
961961
});
962962

963+
apexRepeatTags.forEach(function (location) {
964+
newMessages.push({
965+
message: '<apex:repeat> tags are not allowed in Javascript',
966+
line: location.line,
967+
column: location.column,
968+
ruleId: '(visualforce plugin)',
969+
severity: 2
970+
});
971+
});
972+
963973
newMessages.sort(function (ma, mb) {
964974
return ma.line - mb.line || ma.column - mb.column;
965975
});
@@ -1165,10 +1175,9 @@ var _marked = [dedent].map(regeneratorRuntime.mark);
11651175
var htmlparser2 = __webpack_require__(8);
11661176
var TransformableString = __webpack_require__(2);
11671177

1168-
function iterateScripts(code, options, onChunk) {
1178+
function iterateScripts(code, onChunk) {
11691179
if (!code) return;
11701180

1171-
var xmlMode = options.xmlMode;
11721181
var index = 0;
11731182
var inScript = false;
11741183
var nextType = null;
@@ -1210,6 +1219,11 @@ function iterateScripts(code, options, onChunk) {
12101219

12111220
onopentag(name) {
12121221
// Test if current tag is a valid <script> tag.
1222+
if (name === 'apex:repeat' && inScript) {
1223+
emitChunk('script', parser.startIndex);
1224+
emitChunk('apex:repeat', parser.endIndex + 1);
1225+
//console.log('open tag: ', code.slice(parser.startIndex, parser.endIndex+1));
1226+
}
12131227
if (name !== 'script') return;
12141228
inScript = true;
12151229
emitChunk('html', parser.endIndex + 1);
@@ -1224,6 +1238,12 @@ function iterateScripts(code, options, onChunk) {
12241238
},
12251239

12261240
onclosetag(name) {
1241+
if (name === 'apex:repeat' && inScript) {
1242+
emitChunk('script', parser.startIndex);
1243+
emitChunk('apex:repeat', parser.endIndex + 1);
1244+
// console.log('close tag: ', code.slice(parser.startIndex, parser.endIndex+1));
1245+
}
1246+
12271247
if (name !== 'script' || !inScript) return;
12281248

12291249
inScript = false;
@@ -1238,16 +1258,16 @@ function iterateScripts(code, options, onChunk) {
12381258
emitChunk('script', parser.endIndex + 1);
12391259
}
12401260

1241-
}, { xmlMode: xmlMode === true });
1261+
}, { xmlMode: true });
12421262

12431263
parser.parseComplete(code);
12441264

12451265
emitChunk('html', parser.endIndex + 1, true);
12461266
}
12471267

1248-
function computeIndent(descriptor, previousHTML, slice) {
1268+
function computeIndent(descriptor, previousHTML, codeSlice) {
12491269
if (!descriptor) {
1250-
var indentMatch = /[\n\r]+([ \t]*)/.exec(slice);
1270+
var indentMatch = /[\n\r]+([ \t]*)/.exec(codeSlice);
12511271
return indentMatch ? indentMatch[1] : '';
12521272
}
12531273

@@ -1256,7 +1276,7 @@ function computeIndent(descriptor, previousHTML, slice) {
12561276
return descriptor.spaces;
12571277
}
12581278

1259-
function dedent(indent, slice) {
1279+
function dedent(indent, codeSlice) {
12601280
var hadNonEmptyLine, re, match, newLine, lineIndent, lineText, isEmptyLine, isFirstNonEmptyLine, badIndentation;
12611281
return regeneratorRuntime.wrap(function dedent$(_context) {
12621282
while (1) {
@@ -1266,7 +1286,7 @@ function dedent(indent, slice) {
12661286
re = /(\r\n|\n|\r)([ \t]*)(.*)/g;
12671287

12681288
case 2:
1269-
match = re.exec(slice);
1289+
match = re.exec(codeSlice);
12701290

12711291
if (match) {
12721292
_context.next = 5;
@@ -1334,58 +1354,74 @@ function dedent(indent, slice) {
13341354
}, _marked[0], this);
13351355
}
13361356

1337-
function extract(code, indentDescriptor, xmlMode) {
1357+
function extract(code, indentDescriptor) {
13381358
var badIndentationLines = [];
1359+
var apexRepeatTags = [];
13391360
var transformedCode = new TransformableString(code);
13401361
var lineNumber = 1;
13411362
var previousHTML = '';
13421363

1343-
iterateScripts(code, { xmlMode }, function (chunk) {
1344-
var slice = code.slice(chunk.start, chunk.end);
1345-
1346-
if (chunk.type === 'html' || chunk.type === 'cdata start' || chunk.type === 'cdata end') {
1347-
var newLinesRe = /(?:\r\n|\n|\r)([^\r\n])?/g;
1348-
var lastEmptyLinesLength = 0;
1349-
for (;;) {
1350-
var match = newLinesRe.exec(slice);
1351-
if (!match) break;
1352-
lineNumber += 1;
1353-
lastEmptyLinesLength = !match[1] ? lastEmptyLinesLength + match[0].length : 0;
1354-
}
1355-
transformedCode.replace(chunk.start, chunk.end - lastEmptyLinesLength, '/* HTML */');
1356-
if (chunk.type === 'html') previousHTML = slice;
1357-
} else if (chunk.type === 'script') {
1358-
var _iteratorNormalCompletion = true;
1359-
var _didIteratorError = false;
1360-
var _iteratorError = undefined;
1361-
1362-
try {
1363-
for (var _iterator = dedent(computeIndent(indentDescriptor, previousHTML, slice), slice)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
1364-
var action = _step.value;
1365-
1366-
lineNumber += 1;
1367-
if (action.type === 'dedent') transformedCode.replace(chunk.start + action.from, chunk.start + action.to, '');else if (action.type === 'bad-indent') badIndentationLines.push(lineNumber);
1364+
iterateScripts(code, function (chunk) {
1365+
var codeSlice = code.slice(chunk.start, chunk.end);
1366+
1367+
switch (chunk.type) {
1368+
case 'html':
1369+
previousHTML = codeSlice;
1370+
// falls through
1371+
case 'apex:repeat':
1372+
case 'cdata start':
1373+
case 'cdata end':
1374+
{
1375+
var newLinesRe = /(?:\r\n|\n|\r)([^\r\n])?/g;
1376+
var lastEmptyLinesLength = 0;
1377+
for (;;) {
1378+
var match = newLinesRe.exec(codeSlice);
1379+
if (!match) break;
1380+
lineNumber += 1;
1381+
lastEmptyLinesLength = !match[1] ? lastEmptyLinesLength + match[0].length : 0;
1382+
}
1383+
transformedCode.replace(chunk.start, chunk.end - lastEmptyLinesLength, '/* HTML */');
1384+
break;
13681385
}
1369-
} catch (err) {
1370-
_didIteratorError = true;
1371-
_iteratorError = err;
1372-
} finally {
1386+
// case 'apex:repeat':
1387+
// transformedCode.replace(chunk.start, chunk.end, `/* ${codeSlice} */`)
1388+
// // TODO add message
1389+
// break
1390+
case 'script':
1391+
var _iteratorNormalCompletion = true;
1392+
var _didIteratorError = false;
1393+
var _iteratorError = undefined;
1394+
13731395
try {
1374-
if (!_iteratorNormalCompletion && _iterator.return) {
1375-
_iterator.return();
1396+
for (var _iterator = dedent(computeIndent(indentDescriptor, previousHTML, codeSlice), codeSlice)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
1397+
var action = _step.value;
1398+
1399+
lineNumber += 1;
1400+
if (action.type === 'dedent') transformedCode.replace(chunk.start + action.from, chunk.start + action.to, '');else if (action.type === 'bad-indent') badIndentationLines.push(lineNumber);
13761401
}
1402+
} catch (err) {
1403+
_didIteratorError = true;
1404+
_iteratorError = err;
13771405
} finally {
1378-
if (_didIteratorError) {
1379-
throw _iteratorError;
1406+
try {
1407+
if (!_iteratorNormalCompletion && _iterator.return) {
1408+
_iterator.return();
1409+
}
1410+
} finally {
1411+
if (_didIteratorError) {
1412+
throw _iteratorError;
1413+
}
13801414
}
13811415
}
1382-
}
1416+
1417+
break;
13831418
}
1384-
});
1419+
}); // iterateScripts
13851420

13861421
return {
13871422
code: transformedCode,
1388-
badIndentationLines
1423+
badIndentationLines,
1424+
apexRepeatTags
13891425
};
13901426
}
13911427

@@ -1457,6 +1493,12 @@ var untaintingParents = {
14571493
MapEntry(parentNode, node) {
14581494
// keys are safe, values are not
14591495
return parentNode.key === node;
1496+
},
1497+
VFELMemberExpression() {
1498+
// VFELMemberExpressions such as field[selector] are not untainting
1499+
// However, JSENCODE should be applied to the expression itself,
1500+
// and not the selectors, so we untaint the members of this expression
1501+
return true;
14601502
}
14611503
};
14621504

@@ -1489,17 +1531,17 @@ function isTainting(node) {
14891531
return true;
14901532
}
14911533

1492-
var untainter = untaintingParents[parent.type];
1534+
var untainter = parent && untaintingParents[parent.type];
14931535

14941536
// The parent expression untaints the whole subtree
1495-
if (untainter && untainter(parent, node)) {
1496-
return false;
1497-
} else return isTainting(parent);
1537+
if (untainter && untainter(parent, node)) return false;else return isTainting(parent);
14981538
}
14991539

1500-
function checkIdentifier(node, context) {
1540+
function checkNode(node, context) {
1541+
//console.log(`identifier's `, node.name ,` parent is ${node.parent.type}`)
1542+
15011543
// Not checking taint for system variables except the only user-controlled one
1502-
if (isSafeSystemIdentifier(node.name.toUpperCase())) return;
1544+
if (node.name && isSafeSystemIdentifier(node.name.toUpperCase())) return;
15031545

15041546
if (isTainting(node)) context.report({
15051547
message: 'JSENCODE() must be applied to all rendered Apex variables',
@@ -1523,7 +1565,10 @@ module.exports = {
15231565
create(context) {
15241566
return {
15251567
VFELIdentifier: function VFELIdentifier(node) {
1526-
return checkIdentifier(node, context);
1568+
return checkNode(node, context);
1569+
},
1570+
VFELMemberExpression: function VFELMemberExpression(node) {
1571+
return checkNode(node, context);
15271572
}
15281573
};
15291574
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@salesforce/eslint-plugin-visualforce",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"description": "An ESLint plugin to extract and lint scripts from VisualForce pages",
55
"main": "dist/index.js",
66
"scripts": {
@@ -25,7 +25,7 @@
2525
},
2626
"homepage": "https://github.com/forcedotcom/eslint-plugin-visualforce#readme",
2727
"dependencies": {
28-
"@salesforce/acorn-visualforce": "^1.4.1",
28+
"@salesforce/acorn-visualforce": "^1.4.2",
2929
"htmlparser2": "^3.9.2"
3030
},
3131
"devDependencies": {

0 commit comments

Comments
 (0)