Skip to content

Commit e0c7cb0

Browse files
JS-1333 fix(S7790): Prevent references to target FQNs from raising (#6394)
1 parent 6e95419 commit e0c7cb0

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

packages/jsts/src/rules/S7790/cb.fixture.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ const ejsRequireCompile = ejsRequire.compile(getUserInput()); // Noncompliant {{
101101
const ejsStaticCompile = ejs.compile('<h1>Hello <%= name %></h1>'); // Compliant - static string
102102
const ejsStaticRender = ejs.render('<h1>Hello</h1>'); // Compliant - static string
103103

104+
// Test case 19: pug.compile result called
105+
const compiledFn = pug.compile(template); // Noncompliant {{Make sure this dynamically formatted template is safe here.}}
106+
// ^^^^^^^^^^^
107+
compiledFn(user); // This call should not raise, even though the FQN matches.
108+
104109
// Helper functions for testing
105110
function getUserInput(): string {
106111
return 'user-controlled-template';

packages/jsts/src/rules/S7790/rule.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818

1919
import type { Rule } from 'eslint';
2020
import type estree from 'estree';
21-
import { generateMeta, getFullyQualifiedName } from '../helpers/index.js';
21+
import {
22+
generateMeta,
23+
getFullyQualifiedName,
24+
getUniqueWriteReference,
25+
getVariableFromScope,
26+
} from '../helpers/index.js';
2227
import * as meta from './generated-meta.js';
2328

2429
const templatingFqns: Set<string> = new Set([
@@ -41,7 +46,12 @@ export const rule: Rule.RuleModule = {
4146
const callExpression = node as estree.CallExpression;
4247
const fqn = getFullyQualifiedName(context, callExpression);
4348

44-
if (fqn && templatingFqns.has(fqn) && isQuestionable(callExpression)) {
49+
if (
50+
fqn &&
51+
templatingFqns.has(fqn) &&
52+
!isCallingFunctionResult(context, callExpression) &&
53+
isQuestionable(callExpression)
54+
) {
4555
context.report({
4656
messageId: 'reviewDynamicTemplate',
4757
node: callExpression.callee,
@@ -52,6 +62,27 @@ export const rule: Rule.RuleModule = {
5262
},
5363
};
5464

65+
/**
66+
* Returns true when the callee is a variable holding the result of a prior call,
67+
* e.g. `const fn = pug.compile(tpl); fn(data);` — fn(data) is not a direct
68+
* templating call, so we should not flag it again.
69+
*/
70+
function isCallingFunctionResult(
71+
context: Rule.RuleContext,
72+
callExpression: estree.CallExpression,
73+
): boolean {
74+
const callee = callExpression.callee;
75+
if (callee.type !== 'Identifier') {
76+
return false;
77+
}
78+
const variable = getVariableFromScope(context.sourceCode.getScope(callee), callee.name);
79+
if (!variable || variable.defs.some(def => def.type === 'ImportBinding')) {
80+
return false;
81+
}
82+
const writeRef = getUniqueWriteReference(variable);
83+
return writeRef?.type === 'CallExpression';
84+
}
85+
5586
function isQuestionable(node: estree.CallExpression, index = 0): boolean {
5687
const args = node.arguments;
5788
const templateString = args[index] as estree.Expression | estree.SpreadElement | undefined;

0 commit comments

Comments
 (0)