📄 plugin-test-formatting¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 16 |
| 📦 Imports | 8 |
| 📊 Variables & Constants | 4 |
| 📑 Type Aliases | 3 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'plugin-test-formatting' |
meta.type |
'problem' |
meta.docs.description |
Enforce that eslint-plugin test snippets are correctly formatted |
meta.docs.requiresTypeChecking |
true |
meta.fixable |
'code' |
meta.messages.invalidFormatting |
'This snippet should be formatted correctly. Use the fixer to format the code.' |
meta.messages.invalidFormattingErrorTest |
'This snippet should be formatted correctly. Use the fixer to format the code. Note that the automated fixer may brea... |
meta.messages.noUnnecessaryNoFormat |
'noFormat is unnecessary here. Use the fixer to remove it.' |
meta.messages.prettierException |
'Prettier was unable to format this snippet: {{message}}' |
meta.messages.singleLineQuotes |
'Use quotes (\' or ") for single line tests.' |
meta.messages.templateLiteralEmptyEnds |
'Template literals must start and end with an empty line.' |
meta.messages.templateLiteralLastLineIndent |
'The closing line of the template literal must be indented to align with its parent.' |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { formatWithPrettier: { type: 'boolean', description: 'W... |
defaultOptions |
[ { formatWithPrettier: true, }, ] |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
prettier |
@prettier/sync |
getContextualType |
@typescript-eslint/type-utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
ESLintUtils |
@typescript-eslint/utils |
path |
node:path |
fileURLToPath |
node:url |
createRule |
../util/index.js |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
prettierConfig |
any |
const | prettier.resolveConfig(__dirname) ?? {} |
✗ |
START_OF_LINE_WHITESPACE_MA... |
RegExp |
const | /^( *)/ |
✗ |
BACKTICK_REGEX |
RegExp |
const | //g` |
✗ |
TEMPLATE_EXPR_OPENER |
RegExp |
const | /\$\{/g |
✗ |
Functions¶
create(context: any, [{ formatWithPrettier }]: any): { [x: string]: (test: TSESTree.ObjectExpression, isErrorTes…¶
Parameters:
contextany[{ formatWithPrettier }]any
Returns: { [x: string]: (test: TSESTree.ObjectExpression, isErrorTest?: boolean) => void; 'CallExpression > ObjectExpression > Property[key.name = "valid"] > ArrayExpression': (tests: TSESTree.ArrayExpression) => void; ObjectExpression(node: any): void; }
Calls:
ESLintUtils.getParserServicesservices.program.getTypeCheckerprettier .format(code, { ...prettierConfig, parser: 'typescript', }) .trimEndgetCodeFormattedmessage.replacecontext.reportcheckLiteralcheckTemplateLiteralcheckTaggedTemplateExpressioncheckCallExpressiongetCodeFormattedOrReportoutput.includesfixer.replaceTextescapeForTemplateStringgetSafeWrappingQuoteescapeForStringLiteralfixer.replaceTextRangetext.splitlines[0].trimEndlastLine.trimStartgetExpectedIndentForNode' '.repeatlines.poplines.shiftlines.joincheckForUnnecessaryNoFormatisNoFormatTemplateTagcheckExpressioncheckedObjects.hascheckedObjects.addcheckInvalidTest[ AST_NODE_TYPES.CallExpression, AST_NODE_TYPES.ObjectExpression, 'Property[key.name = "invalid"]', AST_NODE_TYPES.ArrayExpression, AST_NODE_TYPES.ObjectExpression, ].joingetContextualType (from @typescript-eslint/type-utils)services.esTreeNodeToTSNodeMap.getchecker.typeToString/^(TSESLint\.)?RunTests\b/.testcheckValidTest/^(TSESLint\.)?ValidTestCase\b/.test/^(TSESLint\.)?InvalidTestCase\b/.test
Internal Comments:
// ex instanceof Error is false as of @prettier/sync@0.3.0, as is ex instanceof SyntaxError
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (x4)
// formatted string is multiline, then have to use backticks
// ignore template literals with ${expressions} for simplicity
// don't use template strings for single line tests
// prettier will trim out the end of line on save, but eslint will check before then (x2)
// last line can be indented (x2)
// multiline template strings must have an empty first/last line
// remove the empty lines (x4)
// all we do on single line test cases is check format, but there's no formatting to do
// handle cases like 'aa'.trimRight and `aa`.trimRight() (x3)
// delegate object-style tests to the invalid checker (x3)
// valid (x2)
// invalid - errors (x2)
/**
* generic, type-aware handling for any old object
* this is a fallback to handle random variables people declare or object
* literals that are passed via array maps, etc
*/ (x2)
Code
create(context, [{ formatWithPrettier }]) {
const services = ESLintUtils.getParserServices(context);
const checker = services.program.getTypeChecker();
const checkedObjects = new Set<TSESTree.ObjectExpression>();
function getCodeFormatted(code: string): string | FormattingError {
try {
return prettier
.format(code, {
...prettierConfig,
parser: 'typescript',
})
.trimEnd(); // prettier will insert a new line at the end of the code
} catch (ex) {
// ex instanceof Error is false as of @prettier/sync@0.3.0, as is ex instanceof SyntaxError
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(ex as Partial<Error> | undefined)?.constructor?.name !==
'SyntaxError'
) {
throw ex;
}
return ex as FormattingError;
}
}
function getCodeFormattedOrReport(
code: string,
location: TSESTree.Node,
): string | null {
if (formatWithPrettier === false) {
return null;
}
const formatted = getCodeFormatted(code);
if (typeof formatted === 'string') {
return formatted;
}
let message = formatted.message;
if (formatted.codeFrame) {
message = message.replace(`\n${formatted.codeFrame}`, '');
}
if (formatted.loc) {
message = message.replace(/ \(\d+:\d+\)$/, '');
}
context.report({
node: location,
messageId: 'prettierException',
data: {
message,
},
});
return null;
}
function checkExpression(
node: TSESTree.Node | null,
isErrorTest: boolean,
): void {
switch (node?.type) {
case AST_NODE_TYPES.Literal:
checkLiteral(node, isErrorTest);
break;
case AST_NODE_TYPES.TemplateLiteral:
checkTemplateLiteral(node, isErrorTest);
break;
case AST_NODE_TYPES.TaggedTemplateExpression:
checkTaggedTemplateExpression(node, isErrorTest);
break;
case AST_NODE_TYPES.CallExpression:
checkCallExpression(node, isErrorTest);
break;
}
}
function checkLiteral(
literal: TSESTree.Literal,
isErrorTest: boolean,
quoteIn?: string,
): void {
if (typeof literal.value === 'string') {
const output = getCodeFormattedOrReport(literal.value, literal);
if (output && output !== literal.value) {
context.report({
node: literal,
messageId: isErrorTest
? 'invalidFormattingErrorTest'
: 'invalidFormatting',
fix(fixer) {
if (output.includes('\n')) {
// formatted string is multiline, then have to use backticks
return fixer.replaceText(
literal,
`\`${escapeForTemplateString(output)}\``,
);
}
const quote = quoteIn ?? getSafeWrappingQuote(output) ?? "'";
return fixer.replaceText(
literal,
`${quote}${escapeForStringLiteral(output, quote)}${quote}`,
);
},
});
}
}
}
function checkTemplateLiteral(
literal: TSESTree.TemplateLiteral,
isErrorTest: boolean,
isNoFormatTagged = false,
): void {
if (literal.quasis.length > 1) {
// ignore template literals with ${expressions} for simplicity
return;
}
const text = literal.quasis[0].value.cooked;
if (text == null) {
return;
}
if (literal.loc.end.line === literal.loc.start.line) {
// don't use template strings for single line tests
return context.report({
node: literal,
messageId: 'singleLineQuotes',
fix(fixer) {
const quote = getSafeWrappingQuote(text);
if (quote == null) {
return null;
}
return [
fixer.replaceTextRange(
[literal.range[0], literal.range[0] + 1],
quote,
),
fixer.replaceTextRange(
[literal.range[1] - 1, literal.range[1]],
quote,
),
];
},
});
}
const lines = text.split('\n');
const lastLine = lines[lines.length - 1];
// prettier will trim out the end of line on save, but eslint will check before then
const isStartEmpty = lines[0].trimEnd() === '';
// last line can be indented
const isEndEmpty = lastLine.trimStart() === '';
if (!isStartEmpty || !isEndEmpty) {
// multiline template strings must have an empty first/last line
return context.report({
node: literal,
messageId: 'templateLiteralEmptyEnds',
*fix(fixer) {
if (!isStartEmpty) {
yield fixer.replaceTextRange(
[literal.range[0], literal.range[0] + 1],
'`\n',
);
}
if (!isEndEmpty) {
yield fixer.replaceTextRange(
[literal.range[1] - 1, literal.range[1]],
'\n`',
);
}
},
});
}
const parentIndent = getExpectedIndentForNode(
literal,
context.sourceCode.lines,
);
if (lastLine.length !== parentIndent) {
return context.report({
node: literal,
messageId: 'templateLiteralLastLineIndent',
fix(fixer) {
return fixer.replaceTextRange(
[literal.range[1] - lastLine.length - 1, literal.range[1]],
`${' '.repeat(parentIndent)}\``,
);
},
});
}
// remove the empty lines
lines.pop();
lines.shift();
const code = lines.join('\n');
if (isNoFormatTagged) {
if (literal.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) {
checkForUnnecessaryNoFormat(code, literal.parent);
}
return;
}
const formatted = getCodeFormattedOrReport(code, literal);
if (formatted && formatted !== code) {
return context.report({
node: literal,
messageId: isErrorTest
? 'invalidFormattingErrorTest'
: 'invalidFormatting',
fix(fixer) {
return fixer.replaceText(
literal,
`\`
${escapeForTemplateString(formatted)}
${' '.repeat(parentIndent)}\``,
);
},
});
}
}
function isNoFormatTemplateTag(tag: TSESTree.Expression): boolean {
return tag.type === AST_NODE_TYPES.Identifier && tag.name === 'noFormat';
}
function checkForUnnecessaryNoFormat(
text: string | null,
expr: TSESTree.TaggedTemplateExpression,
): void {
if (text == null) {
return;
}
const formatted = getCodeFormatted(text);
if (formatted === text) {
context.report({
node: expr,
messageId: 'noUnnecessaryNoFormat',
fix(fixer) {
if (expr.loc.start.line === expr.loc.end.line) {
return fixer.replaceText(
expr,
`'${escapeForTemplateString(text)}'`,
);
}
return fixer.replaceText(expr.tag, '');
},
});
}
}
function checkTaggedTemplateExpression(
expr: TSESTree.TaggedTemplateExpression,
isErrorTest: boolean,
): void {
if (isNoFormatTemplateTag(expr.tag)) {
const { cooked } = expr.quasi.quasis[0].value;
checkForUnnecessaryNoFormat(cooked, expr);
} else {
return;
}
if (expr.loc.start.line === expr.loc.end.line) {
// all we do on single line test cases is check format, but there's no formatting to do
return;
}
checkTemplateLiteral(
expr.quasi,
isErrorTest,
isNoFormatTemplateTag(expr.tag),
);
}
function checkCallExpression(
callExpr: TSESTree.CallExpression,
isErrorTest: boolean,
): void {
if (callExpr.callee.type !== AST_NODE_TYPES.MemberExpression) {
return;
}
const memberExpr = callExpr.callee;
// handle cases like 'aa'.trimRight and `aa`.trimRight()
checkExpression(memberExpr.object, isErrorTest);
}
function checkInvalidTest(
test: TSESTree.ObjectExpression,
isErrorTest = true,
): void {
if (checkedObjects.has(test)) {
return;
}
checkedObjects.add(test);
for (const prop of test.properties) {
if (
prop.type !== AST_NODE_TYPES.Property ||
prop.computed ||
prop.key.type !== AST_NODE_TYPES.Identifier
) {
continue;
}
if (prop.key.name === 'code') {
checkExpression(prop.value, isErrorTest);
}
}
}
function checkValidTest(tests: TSESTree.ArrayExpression): void {
for (const test of tests.elements) {
switch (test?.type) {
case AST_NODE_TYPES.ObjectExpression:
// delegate object-style tests to the invalid checker
checkInvalidTest(test, false);
break;
default:
checkExpression(test, false);
break;
}
}
}
return {
// valid
'CallExpression > ObjectExpression > Property[key.name = "valid"] > ArrayExpression':
checkValidTest,
// invalid - errors
[[
AST_NODE_TYPES.CallExpression,
AST_NODE_TYPES.ObjectExpression,
'Property[key.name = "invalid"]',
AST_NODE_TYPES.ArrayExpression,
AST_NODE_TYPES.ObjectExpression,
].join(' > ')]: checkInvalidTest,
/**
* generic, type-aware handling for any old object
* this is a fallback to handle random variables people declare or object
* literals that are passed via array maps, etc
*/
ObjectExpression(node): void {
if (checkedObjects.has(node)) {
return;
}
const type = getContextualType(
checker,
services.esTreeNodeToTSNodeMap.get(node),
);
if (!type) {
return;
}
const typeString = checker.typeToString(type);
if (/^(TSESLint\.)?RunTests\b/.test(typeString)) {
checkedObjects.add(node);
for (const prop of node.properties) {
if (
prop.type === AST_NODE_TYPES.SpreadElement ||
prop.computed ||
prop.key.type !== AST_NODE_TYPES.Identifier ||
prop.value.type !== AST_NODE_TYPES.ArrayExpression
) {
continue;
}
switch (prop.key.name) {
case 'valid':
checkValidTest(prop.value);
break;
case 'invalid':
for (const element of prop.value.elements) {
if (element?.type === AST_NODE_TYPES.ObjectExpression) {
checkInvalidTest(element);
}
}
break;
}
}
return;
}
if (/^(TSESLint\.)?ValidTestCase\b/.test(typeString)) {
checkInvalidTest(node);
return;
}
if (/^(TSESLint\.)?InvalidTestCase\b/.test(typeString)) {
checkInvalidTest(node);
for (const testProp of node.properties) {
if (
testProp.type === AST_NODE_TYPES.SpreadElement ||
testProp.computed ||
testProp.key.type !== AST_NODE_TYPES.Identifier ||
testProp.key.name !== 'errors' ||
testProp.value.type !== AST_NODE_TYPES.ArrayExpression
) {
continue;
}
for (const errorElement of testProp.value.elements) {
if (errorElement?.type !== AST_NODE_TYPES.ObjectExpression) {
continue;
}
checkInvalidTest(errorElement);
}
}
}
},
};
}
getExpectedIndentForNode(node: TSESTree.Node, sourceCodeLines: string[]): number¶
Parameters:
nodeTSESTree.NodesourceCodeLinesstring[]
Returns: number
Calls:
START_OF_LINE_WHITESPACE_MATCHER.exec
Internal Comments:
Code
function getExpectedIndentForNode(
node: TSESTree.Node,
sourceCodeLines: string[],
): number {
const lineIdx = node.loc.start.line - 1;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const indent = START_OF_LINE_WHITESPACE_MATCHER.exec(
sourceCodeLines[lineIdx],
)![1];
return indent.length;
}
getSafeWrappingQuote(code: string): "'" | '"' | null¶
Parameters:
codestring
Returns: "'" | '"' | null
Calls:
code.includes
Internal Comments:
Code
function getSafeWrappingQuote(code: string): "'" | '"' | null {
const hasSingleQuote = code.includes("'");
const hasDoubleQuote = code.includes('"');
if (hasSingleQuote && hasDoubleQuote) {
// be lazy and make them fix and escape the quotes manually
return null;
}
return hasSingleQuote ? '"' : "'";
}
escapeForTemplateString(code: string): string¶
Parameters:
codestring
Returns: string
Calls:
fixed.replaceAll
Code
escapeForStringLiteral(code: string, quote: string): string¶
Parameters:
codestringquotestring
Returns: string
Calls:
fixed.replaceAll
Code
Internal helpers¶
Declared inside another function in this file.
getCodeFormatted(code: string): string | FormattingError¶
Parameters:
codestring
Returns: string | FormattingError
Calls:
prettier .format(code, { ...prettierConfig, parser: 'typescript', }) .trimEnd
Internal Comments:
// ex instanceof Error is false as of @prettier/sync@0.3.0, as is ex instanceof SyntaxError
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (x4)
Code
function getCodeFormatted(code: string): string | FormattingError {
try {
return prettier
.format(code, {
...prettierConfig,
parser: 'typescript',
})
.trimEnd(); // prettier will insert a new line at the end of the code
} catch (ex) {
// ex instanceof Error is false as of @prettier/sync@0.3.0, as is ex instanceof SyntaxError
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(ex as Partial<Error> | undefined)?.constructor?.name !==
'SyntaxError'
) {
throw ex;
}
return ex as FormattingError;
}
}
getCodeFormattedOrReport(code: string, location: TSESTree.Node): string | null¶
Parameters:
codestringlocationTSESTree.Node
Returns: string | null
Calls:
getCodeFormattedmessage.replacecontext.report
Code
function getCodeFormattedOrReport(
code: string,
location: TSESTree.Node,
): string | null {
if (formatWithPrettier === false) {
return null;
}
const formatted = getCodeFormatted(code);
if (typeof formatted === 'string') {
return formatted;
}
let message = formatted.message;
if (formatted.codeFrame) {
message = message.replace(`\n${formatted.codeFrame}`, '');
}
if (formatted.loc) {
message = message.replace(/ \(\d+:\d+\)$/, '');
}
context.report({
node: location,
messageId: 'prettierException',
data: {
message,
},
});
return null;
}
checkExpression(node: TSESTree.Node | null, isErrorTest: boolean): void¶
Parameters:
nodeTSESTree.Node | nullisErrorTestboolean
Returns: void
Calls:
checkLiteralcheckTemplateLiteralcheckTaggedTemplateExpressioncheckCallExpression
Code
function checkExpression(
node: TSESTree.Node | null,
isErrorTest: boolean,
): void {
switch (node?.type) {
case AST_NODE_TYPES.Literal:
checkLiteral(node, isErrorTest);
break;
case AST_NODE_TYPES.TemplateLiteral:
checkTemplateLiteral(node, isErrorTest);
break;
case AST_NODE_TYPES.TaggedTemplateExpression:
checkTaggedTemplateExpression(node, isErrorTest);
break;
case AST_NODE_TYPES.CallExpression:
checkCallExpression(node, isErrorTest);
break;
}
}
checkLiteral(literal: TSESTree.Literal, isErrorTest: boolean, quoteIn: string): void¶
Parameters:
literalTSESTree.LiteralisErrorTestbooleanquoteInstring
Returns: void
Calls:
getCodeFormattedOrReportcontext.reportoutput.includesfixer.replaceTextescapeForTemplateStringgetSafeWrappingQuoteescapeForStringLiteral
Internal Comments:
Code
function checkLiteral(
literal: TSESTree.Literal,
isErrorTest: boolean,
quoteIn?: string,
): void {
if (typeof literal.value === 'string') {
const output = getCodeFormattedOrReport(literal.value, literal);
if (output && output !== literal.value) {
context.report({
node: literal,
messageId: isErrorTest
? 'invalidFormattingErrorTest'
: 'invalidFormatting',
fix(fixer) {
if (output.includes('\n')) {
// formatted string is multiline, then have to use backticks
return fixer.replaceText(
literal,
`\`${escapeForTemplateString(output)}\``,
);
}
const quote = quoteIn ?? getSafeWrappingQuote(output) ?? "'";
return fixer.replaceText(
literal,
`${quote}${escapeForStringLiteral(output, quote)}${quote}`,
);
},
});
}
}
}
checkTemplateLiteral(literal: TSESTree.TemplateLiteral, isErrorTest: boolean, isNoFormatTagged: boolean): void¶
Parameters:
literalTSESTree.TemplateLiteralisErrorTestbooleanisNoFormatTaggedboolean
Returns: void
Calls:
context.reportgetSafeWrappingQuotefixer.replaceTextRangetext.splitlines[0].trimEndlastLine.trimStartgetExpectedIndentForNode' '.repeatlines.poplines.shiftlines.joincheckForUnnecessaryNoFormatgetCodeFormattedOrReportfixer.replaceTextescapeForTemplateString
Internal Comments:
// ignore template literals with ${expressions} for simplicity
// don't use template strings for single line tests
// prettier will trim out the end of line on save, but eslint will check before then (x2)
// last line can be indented (x2)
// multiline template strings must have an empty first/last line
// remove the empty lines (x4)
Code
function checkTemplateLiteral(
literal: TSESTree.TemplateLiteral,
isErrorTest: boolean,
isNoFormatTagged = false,
): void {
if (literal.quasis.length > 1) {
// ignore template literals with ${expressions} for simplicity
return;
}
const text = literal.quasis[0].value.cooked;
if (text == null) {
return;
}
if (literal.loc.end.line === literal.loc.start.line) {
// don't use template strings for single line tests
return context.report({
node: literal,
messageId: 'singleLineQuotes',
fix(fixer) {
const quote = getSafeWrappingQuote(text);
if (quote == null) {
return null;
}
return [
fixer.replaceTextRange(
[literal.range[0], literal.range[0] + 1],
quote,
),
fixer.replaceTextRange(
[literal.range[1] - 1, literal.range[1]],
quote,
),
];
},
});
}
const lines = text.split('\n');
const lastLine = lines[lines.length - 1];
// prettier will trim out the end of line on save, but eslint will check before then
const isStartEmpty = lines[0].trimEnd() === '';
// last line can be indented
const isEndEmpty = lastLine.trimStart() === '';
if (!isStartEmpty || !isEndEmpty) {
// multiline template strings must have an empty first/last line
return context.report({
node: literal,
messageId: 'templateLiteralEmptyEnds',
*fix(fixer) {
if (!isStartEmpty) {
yield fixer.replaceTextRange(
[literal.range[0], literal.range[0] + 1],
'`\n',
);
}
if (!isEndEmpty) {
yield fixer.replaceTextRange(
[literal.range[1] - 1, literal.range[1]],
'\n`',
);
}
},
});
}
const parentIndent = getExpectedIndentForNode(
literal,
context.sourceCode.lines,
);
if (lastLine.length !== parentIndent) {
return context.report({
node: literal,
messageId: 'templateLiteralLastLineIndent',
fix(fixer) {
return fixer.replaceTextRange(
[literal.range[1] - lastLine.length - 1, literal.range[1]],
`${' '.repeat(parentIndent)}\``,
);
},
});
}
// remove the empty lines
lines.pop();
lines.shift();
const code = lines.join('\n');
if (isNoFormatTagged) {
if (literal.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) {
checkForUnnecessaryNoFormat(code, literal.parent);
}
return;
}
const formatted = getCodeFormattedOrReport(code, literal);
if (formatted && formatted !== code) {
return context.report({
node: literal,
messageId: isErrorTest
? 'invalidFormattingErrorTest'
: 'invalidFormatting',
fix(fixer) {
return fixer.replaceText(
literal,
`\`
${escapeForTemplateString(formatted)}
${' '.repeat(parentIndent)}\``,
);
},
});
}
}
isNoFormatTemplateTag(tag: TSESTree.Expression): boolean¶
Parameters:
tagTSESTree.Expression
Returns: boolean
Code
checkForUnnecessaryNoFormat(text: string | null, expr: TSESTree.TaggedTemplateExpression): void¶
Parameters:
textstring | nullexprTSESTree.TaggedTemplateExpression
Returns: void
Calls:
getCodeFormattedcontext.reportfixer.replaceTextescapeForTemplateString
Code
function checkForUnnecessaryNoFormat(
text: string | null,
expr: TSESTree.TaggedTemplateExpression,
): void {
if (text == null) {
return;
}
const formatted = getCodeFormatted(text);
if (formatted === text) {
context.report({
node: expr,
messageId: 'noUnnecessaryNoFormat',
fix(fixer) {
if (expr.loc.start.line === expr.loc.end.line) {
return fixer.replaceText(
expr,
`'${escapeForTemplateString(text)}'`,
);
}
return fixer.replaceText(expr.tag, '');
},
});
}
}
checkTaggedTemplateExpression(expr: TSESTree.TaggedTemplateExpression, isErrorTest: boolean): void¶
Parameters:
exprTSESTree.TaggedTemplateExpressionisErrorTestboolean
Returns: void
Calls:
isNoFormatTemplateTagcheckForUnnecessaryNoFormatcheckTemplateLiteral
Internal Comments:
Code
function checkTaggedTemplateExpression(
expr: TSESTree.TaggedTemplateExpression,
isErrorTest: boolean,
): void {
if (isNoFormatTemplateTag(expr.tag)) {
const { cooked } = expr.quasi.quasis[0].value;
checkForUnnecessaryNoFormat(cooked, expr);
} else {
return;
}
if (expr.loc.start.line === expr.loc.end.line) {
// all we do on single line test cases is check format, but there's no formatting to do
return;
}
checkTemplateLiteral(
expr.quasi,
isErrorTest,
isNoFormatTemplateTag(expr.tag),
);
}
checkCallExpression(callExpr: TSESTree.CallExpression, isErrorTest: boolean): void¶
Parameters:
callExprTSESTree.CallExpressionisErrorTestboolean
Returns: void
Calls:
checkExpression
Internal Comments:
Code
function checkCallExpression(
callExpr: TSESTree.CallExpression,
isErrorTest: boolean,
): void {
if (callExpr.callee.type !== AST_NODE_TYPES.MemberExpression) {
return;
}
const memberExpr = callExpr.callee;
// handle cases like 'aa'.trimRight and `aa`.trimRight()
checkExpression(memberExpr.object, isErrorTest);
}
checkInvalidTest(test: TSESTree.ObjectExpression, isErrorTest: boolean): void¶
Parameters:
testTSESTree.ObjectExpressionisErrorTestboolean
Returns: void
Calls:
checkedObjects.hascheckedObjects.addcheckExpression
Code
function checkInvalidTest(
test: TSESTree.ObjectExpression,
isErrorTest = true,
): void {
if (checkedObjects.has(test)) {
return;
}
checkedObjects.add(test);
for (const prop of test.properties) {
if (
prop.type !== AST_NODE_TYPES.Property ||
prop.computed ||
prop.key.type !== AST_NODE_TYPES.Identifier
) {
continue;
}
if (prop.key.name === 'code') {
checkExpression(prop.value, isErrorTest);
}
}
}
checkValidTest(tests: TSESTree.ArrayExpression): void¶
Parameters:
testsTSESTree.ArrayExpression
Returns: void
Calls:
checkInvalidTestcheckExpression
Internal Comments:
Code
function checkValidTest(tests: TSESTree.ArrayExpression): void {
for (const test of tests.elements) {
switch (test?.type) {
case AST_NODE_TYPES.ObjectExpression:
// delegate object-style tests to the invalid checker
checkInvalidTest(test, false);
break;
default:
checkExpression(test, false);
break;
}
}
}
Type Aliases¶
Options¶
type Options = [
{
// This option exists so that rules like type-annotation-spacing can exist without every test needing a prettier-ignore
formatWithPrettier?: boolean;
},
];
MessageIds¶
type MessageIds = | 'invalidFormatting'
| 'invalidFormattingErrorTest'
| 'noUnnecessaryNoFormat'
| 'prettierException'
| 'singleLineQuotes'
| 'templateLiteralEmptyEnds'
| 'templateLiteralLastLineIndent';
FormattingError¶
Generated by Syntax Scribe