eslint-plugin-sfmc 3.0.0 → 4.0.1

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/src/index.js CHANGED
@@ -15,6 +15,7 @@ import * as handlebarsParser from './handlebars-parser.js';
15
15
  // ── AMPscript rules ───────────────────────────────────────────────────────────
16
16
 
17
17
  import ampNoUnknownFunction from './rules/amp/no-unknown-function.js';
18
+ import ampNoMcnUnsupported from './rules/amp/no-mcn-unsupported.js';
18
19
  import ampNoVariableRedeclaration from './rules/amp/no-variable-redeclaration.js';
19
20
  import ampSetRequiresTarget from './rules/amp/set-requires-target.js';
20
21
  import ampNoEmptyBlock from './rules/amp/no-empty-block.js';
@@ -40,6 +41,7 @@ import ampNoNestedAmpscriptDelimiter from './rules/amp/no-nested-ampscript-delim
40
41
  import ssjsRequirePlatformLoad from './rules/ssjs/require-platform-load.js';
41
42
  import ssjsNoUnsupportedSyntax from './rules/ssjs/no-unsupported-syntax.js';
42
43
  import ssjsNoUnknownFunction from './rules/ssjs/no-unknown-function.js';
44
+ import ssjsNoMcnUnsupported from './rules/ssjs/no-mcn-unsupported.js';
43
45
  import ssjsNoDeprecatedFunction from './rules/ssjs/no-deprecated-function.js';
44
46
  import ssjsNoPropertyCall from './rules/ssjs/no-property-call.js';
45
47
  import ssjsPlatformFunctionArity from './rules/ssjs/platform-function-arity.js';
@@ -58,7 +60,7 @@ import ssjsCoreMethodArity from './rules/ssjs/ssjs-core-method-arity.js';
58
60
  // ── Handlebars (MCN) rules ──────────────────────────────────────────────────────
59
61
 
60
62
  import hbsNoUnknownHelper from './rules/hbs/no-unknown-helper.js';
61
- import hbsHelperTooNewForTarget from './rules/hbs/helper-too-new-for-target.js';
63
+ import hbsNoMcnUnsupported from './rules/hbs/no-mcn-unsupported.js';
62
64
  import hbsNoUnknownBinding from './rules/hbs/no-unknown-binding.js';
63
65
  import hbsHelperArity from './rules/hbs/helper-arity.js';
64
66
  import hbsNoUnsupportedConstruct from './rules/hbs/no-unsupported-construct.js';
@@ -83,6 +85,7 @@ const plugin = {
83
85
  rules: {
84
86
  // AMPscript rules (amp- prefix)
85
87
  'amp-no-unknown-function': ampNoUnknownFunction,
88
+ 'amp-no-mcn-unsupported': ampNoMcnUnsupported,
86
89
  'amp-no-var-redeclaration': ampNoVariableRedeclaration,
87
90
  'amp-set-requires-target': ampSetRequiresTarget,
88
91
  'amp-no-empty-block': ampNoEmptyBlock,
@@ -107,6 +110,7 @@ const plugin = {
107
110
  'ssjs-require-platform-load': ssjsRequirePlatformLoad,
108
111
  'ssjs-no-unsupported-syntax': ssjsNoUnsupportedSyntax,
109
112
  'ssjs-no-unknown-function': ssjsNoUnknownFunction,
113
+ 'ssjs-no-mcn-unsupported': ssjsNoMcnUnsupported,
110
114
  'ssjs-no-deprecated-function': ssjsNoDeprecatedFunction,
111
115
  'ssjs-no-property-call': ssjsNoPropertyCall,
112
116
  'ssjs-platform-function-arity': ssjsPlatformFunctionArity,
@@ -124,7 +128,7 @@ const plugin = {
124
128
 
125
129
  // Handlebars (MCN) rules (hbs- prefix)
126
130
  'hbs-no-unknown-helper': hbsNoUnknownHelper,
127
- 'hbs-helper-too-new-for-target': hbsHelperTooNewForTarget,
131
+ 'hbs-no-mcn-unsupported': hbsNoMcnUnsupported,
128
132
  'hbs-no-unknown-binding': hbsNoUnknownBinding,
129
133
  'hbs-helper-arity': hbsHelperArity,
130
134
  'hbs-no-unsupported-construct': hbsNoUnsupportedConstruct,
@@ -141,11 +145,13 @@ const plugin = {
141
145
 
142
146
  /**
143
147
  * SSJS rules for MCN targets.
144
- * All quality rules are disabled — only the presence of SSJS is flagged,
145
- * because SSJS as a whole must be deleted when targeting Marketing Cloud Next.
148
+ * All quality rules are disabled — only the presence of SSJS is flagged, via the
149
+ * dedicated `ssjs-no-mcn-unsupported` rule, because SSJS as a whole must be
150
+ * deleted when targeting Marketing Cloud Next.
146
151
  */
147
152
  const ssjsMcnRules = {
148
- 'sfmc/ssjs-no-unknown-function': ['error', { target: 'next' }],
153
+ 'sfmc/ssjs-no-mcn-unsupported': 'error',
154
+ 'sfmc/ssjs-no-unknown-function': 'off',
149
155
  'sfmc/ssjs-require-platform-load': 'off',
150
156
  'sfmc/ssjs-no-unsupported-syntax': 'off',
151
157
  'sfmc/ssjs-no-deprecated-function': 'off',
@@ -191,7 +197,7 @@ const ampStrictRules = {
191
197
  'sfmc/amp-no-unknown-function': 'error',
192
198
  'sfmc/amp-no-var-redeclaration': 'error',
193
199
  'sfmc/amp-set-requires-target': 'error',
194
- 'sfmc/amp-no-empty-block': 'error',
200
+ 'sfmc/amp-no-empty-block': 'warn',
195
201
  'sfmc/amp-no-smart-quotes': 'error',
196
202
  'sfmc/amp-prefer-attribute-value': 'warn',
197
203
  'sfmc/amp-no-loop-counter-assign': 'error',
@@ -199,8 +205,8 @@ const ampStrictRules = {
199
205
  'sfmc/amp-require-variable-declaration': 'warn',
200
206
  'sfmc/amp-function-arity': 'error',
201
207
  'sfmc/amp-arg-types': 'error',
202
- 'sfmc/amp-no-email-excluded-function': ['error', { context: 'email' }],
203
- 'sfmc/amp-no-deprecated-function': 'error',
208
+ 'sfmc/amp-no-email-excluded-function': 'off',
209
+ 'sfmc/amp-no-deprecated-function': 'warn',
204
210
  'sfmc/amp-naming-convention': ['error', { format: 'camelCase' }],
205
211
  'sfmc/amp-no-empty-then': 'error',
206
212
  'sfmc/amp-require-rowcount-check': 'error',
@@ -244,7 +250,7 @@ const ssjsStrictRules = {
244
250
  'sfmc/ssjs-require-hasownproperty': 'error',
245
251
  'sfmc/ssjs-require-platform-load-order': 'error',
246
252
  'sfmc/ssjs-no-hardcoded-credentials': 'error',
247
- 'sfmc/ssjs-prefer-platform-load-version': 'error',
253
+ 'sfmc/ssjs-prefer-platform-load-version': 'warn',
248
254
  'sfmc/ssjs-no-unavailable-method': 'warn',
249
255
  'sfmc/ssjs-prefer-parsejson-safe-arg': 'error',
250
256
  'sfmc/ssjs-no-switch-default': 'error',
@@ -266,7 +272,7 @@ const hbsNextRules = {
266
272
  'sfmc/hbs-no-unknown-binding': 'error',
267
273
  'sfmc/hbs-helper-arity': 'error',
268
274
  'sfmc/hbs-no-unsupported-construct': 'error',
269
- 'sfmc/hbs-helper-too-new-for-target': 'off',
275
+ 'sfmc/hbs-no-mcn-unsupported': 'error',
270
276
  };
271
277
 
272
278
  /**
@@ -279,7 +285,7 @@ const hbsOffRules = {
279
285
  'sfmc/hbs-no-unknown-binding': 'off',
280
286
  'sfmc/hbs-helper-arity': 'off',
281
287
  'sfmc/hbs-no-unsupported-construct': 'off',
282
- 'sfmc/hbs-helper-too-new-for-target': 'off',
288
+ 'sfmc/hbs-no-mcn-unsupported': 'off',
283
289
  };
284
290
 
285
291
  /** Shared languageOptions for linting virtual `.hbs` files. */
@@ -449,7 +455,7 @@ plugin.configs = {
449
455
  files: ['**/*.ampscript', '**/*.amp'],
450
456
  rules: {
451
457
  ...ampRecommendedRules,
452
- 'sfmc/amp-no-unknown-function': ['error', { target: 'next' }],
458
+ 'sfmc/amp-no-mcn-unsupported': 'error',
453
459
  },
454
460
  },
455
461
 
@@ -480,7 +486,7 @@ plugin.configs = {
480
486
  files: ['**/*.ampscript', '**/*.amp'],
481
487
  rules: {
482
488
  ...ampRecommendedRules,
483
- 'sfmc/amp-no-unknown-function': ['error', { target: 'next' }],
489
+ 'sfmc/amp-no-mcn-unsupported': 'error',
484
490
  },
485
491
  },
486
492
  {
@@ -513,7 +519,7 @@ plugin.configs = {
513
519
  files: ['**/*.html/*.amp'],
514
520
  rules: {
515
521
  ...ampRecommendedRules,
516
- 'sfmc/amp-no-unknown-function': ['error', { target: 'next' }],
522
+ 'sfmc/amp-no-mcn-unsupported': 'error',
517
523
  },
518
524
  },
519
525
  {
@@ -548,7 +554,7 @@ plugin.configs = {
548
554
  files: ['**/*.ampscript', '**/*.amp'],
549
555
  rules: {
550
556
  ...ampStrictRules,
551
- 'sfmc/amp-no-unknown-function': ['error', { target: 'next' }],
557
+ 'sfmc/amp-no-mcn-unsupported': 'error',
552
558
  },
553
559
  },
554
560
  {
@@ -575,7 +581,7 @@ plugin.configs = {
575
581
  files: ['**/*.html/*.amp'],
576
582
  rules: {
577
583
  ...ampStrictRules,
578
- 'sfmc/amp-no-unknown-function': ['error', { target: 'next' }],
584
+ 'sfmc/amp-no-mcn-unsupported': 'error',
579
585
  },
580
586
  },
581
587
  {
@@ -0,0 +1,102 @@
1
+ import { functionNames, functionLookup, getMcnApiVersion } from 'ampscript-data';
2
+
3
+ /**
4
+ * Flags AMPscript functions that cannot be used when targeting Marketing Cloud
5
+ * Next, and functions that work in MCN AMPscript but have no Handlebars
6
+ * equivalent (so they block a Handlebars migration).
7
+ *
8
+ * Each function carries an `mcnSince` value = the MCN API version it first
9
+ * became available in, or `null`/unset when it was never supported in MCN.
10
+ *
11
+ * `apiVersion` semantics (shared with the SSJS and Handlebars MCN rules):
12
+ * - Not set / null: flag every function whose `mcnSince` is null/unset. Any
13
+ * function supported at some point in MCN (numeric `mcnSince`) passes.
14
+ * - Set to N: flag everything the null case flags, PLUS everything whose
15
+ * `mcnSince` is greater than N (too new for the target). Pass iff
16
+ * `mcnSince != null && mcnSince <= N`.
17
+ *
18
+ * Unknown-function reporting stays in `amp-no-unknown-function`; this rule only
19
+ * evaluates functions that exist in the AMPscript catalog.
20
+ */
21
+ export default {
22
+ meta: {
23
+ type: 'problem',
24
+ docs: {
25
+ description:
26
+ 'Disallow AMPscript functions that are not available in the targeted Marketing Cloud Next API version',
27
+ recommended: true,
28
+ },
29
+ messages: {
30
+ notSupportedInMcn: "'{{name}}' is not supported in Marketing Cloud Next.",
31
+ tooNewForTarget:
32
+ "'{{name}}' was introduced in Marketing Cloud Next API version {{since}}, which is newer than the targeted version {{target}}.",
33
+ noHandlebarsEquivalent:
34
+ "'{{name}}' is supported by Marketing Cloud Next AMPscript but has no Handlebars for Marketing Cloud Next equivalent. It cannot be migrated to a Handlebars helper.",
35
+ },
36
+ schema: [
37
+ {
38
+ type: 'object',
39
+ properties: {
40
+ apiVersion: {
41
+ type: 'number',
42
+ description:
43
+ 'The targeted Marketing Cloud Next API version (e.g. 65 = Winter \u{2019}26, 67 = Summer \u{2019}26). Functions newer than this are flagged.',
44
+ },
45
+ },
46
+ additionalProperties: false,
47
+ },
48
+ ],
49
+ },
50
+
51
+ create(context) {
52
+ const options = context.options[0] ?? {};
53
+ const apiVersion = typeof options.apiVersion === 'number' ? options.apiVersion : null;
54
+
55
+ return {
56
+ FunctionCall(node) {
57
+ const lower = node.name.toLowerCase();
58
+
59
+ // Unknown functions are handled by amp-no-unknown-function.
60
+ if (!functionNames.has(lower)) {
61
+ return;
62
+ }
63
+
64
+ const since = getMcnApiVersion(lower);
65
+
66
+ // Never supported in MCN → always flag.
67
+ if (since === null) {
68
+ context.report({
69
+ node,
70
+ messageId: 'notSupportedInMcn',
71
+ data: { name: node.name },
72
+ });
73
+ return;
74
+ }
75
+
76
+ // Supported in MCN but newer than the targeted API version.
77
+ if (apiVersion !== null && since > apiVersion) {
78
+ context.report({
79
+ node,
80
+ messageId: 'tooNewForTarget',
81
+ data: {
82
+ name: node.name,
83
+ since: String(since),
84
+ target: String(apiVersion),
85
+ },
86
+ });
87
+ return;
88
+ }
89
+
90
+ // Works in MCN AMPscript but has no Handlebars counterpart, so it
91
+ // cannot be migrated to a Handlebars helper.
92
+ if (functionLookup.get(lower)?.mcnHandlebarsGap === true) {
93
+ context.report({
94
+ node,
95
+ messageId: 'noHandlebarsEquivalent',
96
+ data: { name: node.name },
97
+ });
98
+ }
99
+ },
100
+ };
101
+ },
102
+ };
@@ -1,12 +1,16 @@
1
1
  // The processor extracts each AMPscript region into its own virtual .amp file,
2
2
  // including the outer delimiters (%%[...]/%%] or <script>...</script>).
3
- // Any %%[ or %%= found INSIDE the outer delimiters is nested and invalid.
4
- const DELIMITER_RE = /%%\[|%%=/g;
3
+ // Any %%[ or %%= found INSIDE an already-open block is nested and invalid.
4
+ //
5
+ // A raw .amp file may contain several *sibling* blocks (e.g. one %%[...]%% block
6
+ // followed by another). Those are not nested. To distinguish nesting from
7
+ // siblings we scan every open/close token in document order and track depth:
8
+ // an opener is only nested when depth > 0.
9
+ const TOKEN_RE = /%%\[|%%=|\]%%|=%%/g;
5
10
  const SCRIPT_TAG_RE = /^[\n]*<script\b/i;
6
- const BLOCK_OPEN_RE = /^[\n]*(%%\[)/;
7
- const INLINE_OPEN_RE = /^[\n]*(%%=)/;
8
- const BLOCK_CLOSE_SUFFIX = ']%%';
9
- const INLINE_CLOSE_SUFFIX = '=%%';
11
+
12
+ const OPEN_TOKENS = new Set(['%%[', '%%=']);
13
+ const CLOSE_FOR_OPEN = { '%%[': ']%%', '%%=': '=%%' };
10
14
 
11
15
  export default {
12
16
  meta: {
@@ -25,76 +29,64 @@ export default {
25
29
  },
26
30
 
27
31
  create(context) {
32
+ const sourceCode = context.sourceCode;
33
+
34
+ const reportNested = (text, match, isScriptBlock) => {
35
+ const delimiter = match[0];
36
+ const start = match.index;
37
+ const end = start + delimiter.length;
38
+ const closeToken = CLOSE_FOR_OPEN[delimiter];
39
+ const closeIndex = text.indexOf(closeToken, end);
40
+
41
+ context.report({
42
+ loc: {
43
+ start: sourceCode.getLocFromIndex(start),
44
+ end: sourceCode.getLocFromIndex(end),
45
+ },
46
+ messageId: isScriptBlock ? 'nestedDelimiterInScript' : 'nestedDelimiter',
47
+ data: { delimiter },
48
+ fix:
49
+ closeIndex === -1
50
+ ? (fixer) => fixer.removeRange([start, end])
51
+ : (fixer) => [
52
+ fixer.removeRange([closeIndex, closeIndex + closeToken.length]),
53
+ fixer.removeRange([start, end]),
54
+ ],
55
+ });
56
+ };
57
+
28
58
  return {
29
59
  Program() {
30
- const sourceCode = context.sourceCode;
31
60
  const text = sourceCode.getText();
32
61
  const isScriptBlock = SCRIPT_TAG_RE.test(text);
33
62
 
34
- // For %%[...]%% and %%=...=%% blocks the processor includes the outer
35
- // delimiters. Record their positions so we can skip the outermost ones.
36
- let outerOpenStart = -1;
37
- let outerCloseStart = -1;
38
- let outerCloseSuffix = BLOCK_CLOSE_SUFFIX;
39
-
40
- if (!isScriptBlock) {
41
- const blockOpenMatch = BLOCK_OPEN_RE.exec(text);
42
- const inlineOpenMatch = INLINE_OPEN_RE.exec(text);
43
-
44
- if (blockOpenMatch) {
45
- outerOpenStart = blockOpenMatch.index + blockOpenMatch[0].length - 3;
46
- outerCloseSuffix = BLOCK_CLOSE_SUFFIX;
47
- } else if (inlineOpenMatch) {
48
- outerOpenStart = inlineOpenMatch.index + inlineOpenMatch[0].length - 3;
49
- outerCloseSuffix = INLINE_CLOSE_SUFFIX;
50
- }
51
-
52
- // The outer close is the last occurrence of the matching close token
53
- const lastClose = text.lastIndexOf(outerCloseSuffix);
54
- if (lastClose !== -1) {
55
- outerCloseStart = lastClose;
63
+ if (isScriptBlock) {
64
+ // Inside a <script language="ampscript"> block the whole body is
65
+ // already AMPscript; every %%[ / %%= delimiter is redundant.
66
+ TOKEN_RE.lastIndex = 0;
67
+ let match;
68
+ while ((match = TOKEN_RE.exec(text)) !== null) {
69
+ if (OPEN_TOKENS.has(match[0])) {
70
+ reportNested(text, match, true);
71
+ }
56
72
  }
73
+ return;
57
74
  }
58
75
 
59
- DELIMITER_RE.lastIndex = 0;
76
+ // Depth-based scan for raw %%[...]%% / %%=...=%% content. Sibling blocks
77
+ // return depth to 0 between them, so their openers are not flagged.
78
+ let depth = 0;
79
+ TOKEN_RE.lastIndex = 0;
60
80
  let match;
61
- while ((match = DELIMITER_RE.exec(text)) !== null) {
62
- const delimiter = match[0];
63
- const start = match.index;
64
-
65
- // Skip the outermost %%[ opener (it is the block wrapper, not nested)
66
- if (!isScriptBlock && start === outerOpenStart) {
67
- continue;
81
+ while ((match = TOKEN_RE.exec(text)) !== null) {
82
+ if (OPEN_TOKENS.has(match[0])) {
83
+ if (depth > 0) {
84
+ reportNested(text, match, false);
85
+ }
86
+ depth++;
87
+ } else {
88
+ depth = Math.max(0, depth - 1);
68
89
  }
69
-
70
- const end = start + delimiter.length;
71
- const closeToken = delimiter === '%%[' ? ']%%' : '=%%';
72
-
73
- // Find the matching close token for the fix (first occurrence after this open)
74
- let closeIndex = text.indexOf(closeToken, end);
75
- // Don't use the outer ]%% as the fix target for an inner %%[
76
- if (!isScriptBlock && closeIndex === outerCloseStart) {
77
- closeIndex = -1;
78
- }
79
-
80
- context.report({
81
- loc: {
82
- start: sourceCode.getLocFromIndex(start),
83
- end: sourceCode.getLocFromIndex(end),
84
- },
85
- messageId: isScriptBlock ? 'nestedDelimiterInScript' : 'nestedDelimiter',
86
- data: { delimiter },
87
- fix:
88
- closeIndex === -1
89
- ? (fixer) => fixer.removeRange([start, end])
90
- : (fixer) => [
91
- fixer.removeRange([
92
- closeIndex,
93
- closeIndex + closeToken.length,
94
- ]),
95
- fixer.removeRange([start, end]),
96
- ],
97
- });
98
90
  }
99
91
  },
100
92
  };
@@ -1,4 +1,4 @@
1
- const SMART_QUOTE_RE = /[\u{2018}\u{2019}\u{201C}\u{201D}\u{201A}\u{201E}\u{2039}\u{203A}]/u;
1
+ const SMART_QUOTE_RE = /[\u{2018}\u{2019}\u{201C}\u{201D}\u{201A}\u{201E}\u{2039}\u{203A}]/gu;
2
2
 
3
3
  const SMART_QUOTES = {
4
4
  '\u{2018}': 'left single curly quote \u{2018}',
@@ -30,7 +30,7 @@ export default {
30
30
  fixable: 'code',
31
31
  docs: {
32
32
  description:
33
- 'Disallow smart/curly quotes in string literals (AMPscript requires ASCII quotes)',
33
+ 'Disallow smart/curly quotes in AMPscript (AMPscript requires ASCII quotes)',
34
34
  recommended: true,
35
35
  },
36
36
  messages: {
@@ -41,36 +41,30 @@ export default {
41
41
  },
42
42
 
43
43
  create(context) {
44
+ const sourceCode = context.sourceCode;
45
+
44
46
  return {
45
- StringLiteral(node) {
46
- if (SMART_QUOTE_RE.test(node.value)) {
47
- for (const [char, kind] of Object.entries(SMART_QUOTES)) {
48
- if (node.value.includes(char)) {
49
- context.report({
50
- node,
51
- messageId: 'smartQuote',
52
- data: { kind },
53
- fix(fixer) {
54
- const fixed = node.value.replaceAll(
55
- /[\u{2018}\u{2019}\u{201C}\u{201D}\u{201A}\u{201E}\u{2039}\u{203A}]/gu,
56
- (c) => SMART_TO_ASCII[c],
57
- );
58
- const q = node.quote;
59
- // If the replacement introduces the outer delimiter, try switching quotes.
60
- if (fixed.includes(q)) {
61
- const other = q === '"' ? "'" : '"';
62
- if (!fixed.includes(other)) {
63
- return fixer.replaceText(node, other + fixed + other);
64
- }
65
- // Both quote chars present — cannot safely auto-fix.
66
- return null;
67
- }
68
- return fixer.replaceText(node, q + fixed + q);
69
- },
70
- });
71
- return;
72
- }
73
- }
47
+ // The AMPscript parser tokenizes string literals as RAW + IDENTIFIER
48
+ // tokens rather than a StringLiteral node, so a node listener never
49
+ // matches. Scan the virtual .amp region's raw text instead — the whole
50
+ // extracted file is AMPscript, mirroring the LSP smart-quote check.
51
+ Program() {
52
+ const text = sourceCode.getText();
53
+ SMART_QUOTE_RE.lastIndex = 0;
54
+ let match;
55
+ while ((match = SMART_QUOTE_RE.exec(text)) !== null) {
56
+ const char = match[0];
57
+ const start = match.index;
58
+ const end = start + char.length;
59
+ context.report({
60
+ loc: {
61
+ start: sourceCode.getLocFromIndex(start),
62
+ end: sourceCode.getLocFromIndex(end),
63
+ },
64
+ messageId: 'smartQuote',
65
+ data: { kind: SMART_QUOTES[char] },
66
+ fix: (fixer) => fixer.replaceTextRange([start, end], SMART_TO_ASCII[char]),
67
+ });
74
68
  }
75
69
  },
76
70
  };
@@ -1,4 +1,4 @@
1
- import { functionNames, functionLookup, isMcnSupported } from 'ampscript-data';
1
+ import { functionNames } from 'ampscript-data';
2
2
 
3
3
  export default {
4
4
  meta: {
@@ -11,30 +11,11 @@ export default {
11
11
  messages: {
12
12
  unknownFunction:
13
13
  "'{{name}}' is not a recognized AMPscript function. AMPscript does not support custom functions.",
14
- notSupportedInMcn: "'{{name}}' is not supported in Marketing Cloud Next.",
15
- noHandlebarsEquivalent:
16
- "'{{name}}' is supported by Marketing Cloud Next AMPscript but has no Handlebars for Marketing Cloud Next equivalent. It cannot be migrated to a Handlebars helper.",
17
14
  },
18
- schema: [
19
- {
20
- type: 'object',
21
- properties: {
22
- target: {
23
- type: 'string',
24
- enum: ['engagement', 'next'],
25
- description:
26
- "Target platform. Set to 'next' to additionally flag functions not available in Marketing Cloud Next.",
27
- },
28
- },
29
- additionalProperties: false,
30
- },
31
- ],
15
+ schema: [],
32
16
  },
33
17
 
34
18
  create(context) {
35
- const options = context.options[0] ?? {};
36
- const isTargetNext = options.target === 'next';
37
-
38
19
  return {
39
20
  FunctionCall(node) {
40
21
  const lower = node.name.toLowerCase();
@@ -45,26 +26,6 @@ export default {
45
26
  messageId: 'unknownFunction',
46
27
  data: { name: node.name },
47
28
  });
48
- return;
49
- }
50
-
51
- if (isTargetNext && !isMcnSupported(lower)) {
52
- context.report({
53
- node,
54
- messageId: 'notSupportedInMcn',
55
- data: { name: node.name },
56
- });
57
- return;
58
- }
59
-
60
- // Category C: the function works in MCN AMPscript but has no
61
- // Handlebars counterpart, so it cannot be migrated to Handlebars.
62
- if (isTargetNext && functionLookup.get(lower)?.mcnHandlebarsGap === true) {
63
- context.report({
64
- node,
65
- messageId: 'noHandlebarsEquivalent',
66
- data: { name: node.name },
67
- });
68
29
  }
69
30
  },
70
31
  };