mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
623 lines
18 KiB
JavaScript
623 lines
18 KiB
JavaScript
// TypeScript 7 is a native CLI; AST consumers still need the legacy JavaScript API.
|
|
import ts from 'typescript-api'
|
|
|
|
import {
|
|
directReturnFunctionName,
|
|
isAssignedToRenderedVariable,
|
|
isRenderedJsxExpression,
|
|
isReturnedByRenderedFunction
|
|
} from './mobile-localization-rendered-variable.mjs'
|
|
|
|
const NOTIFICATION_CHANNEL_NAME_NODES = new WeakMap()
|
|
|
|
const USER_VISIBLE_JSX_ATTRIBUTES = new Set([
|
|
'ariaLabel',
|
|
'aria-label',
|
|
'aria-description',
|
|
'accessibilityHint',
|
|
'accessibilityLabel',
|
|
'alt',
|
|
'description',
|
|
'emptyText',
|
|
'helperText',
|
|
'keywords',
|
|
'label',
|
|
'message',
|
|
'placeholder',
|
|
'subtitle',
|
|
'text',
|
|
'title',
|
|
'toggleDescription',
|
|
'tooltip'
|
|
])
|
|
const USER_VISIBLE_OBJECT_KEYS = new Set([
|
|
'ariaLabel',
|
|
'accessibilityHint',
|
|
'accessibilityLabel',
|
|
'badge',
|
|
'description',
|
|
'emptyText',
|
|
'error',
|
|
'helperText',
|
|
'keywords',
|
|
'label',
|
|
'message',
|
|
'placeholder',
|
|
'subject',
|
|
'subtitle',
|
|
'successToast',
|
|
'title',
|
|
'toggleDescription',
|
|
'tooltip'
|
|
])
|
|
const MOBILE_USER_VISIBLE_JSX_ATTRIBUTES = new Set(['fallback'])
|
|
const MOBILE_USER_VISIBLE_OBJECT_KEYS = new Set([
|
|
'desc',
|
|
'detail',
|
|
'hint',
|
|
'instructions',
|
|
'question'
|
|
])
|
|
const MOBILE_USER_VISIBLE_PROPERTY_SUFFIX_RE =
|
|
/(?:Caption|Description|Heading|Hint|Label|Message|Notice|Placeholder|Subtitle|Summary|Text|Title|Tooltip|Warning)$/
|
|
const USER_VISIBLE_COLLECTION_SUFFIX_RE =
|
|
/(?:Captions|Descriptions|Headings|Hints|Labels|Messages|Notices|Placeholders|Subtitles|Summaries|Text|Titles|Tooltips|Warnings)$|_(?:CAPTIONS|DESCRIPTIONS|HEADINGS|HINTS|LABELS|MESSAGES|NOTICES|PLACEHOLDERS|SUBTITLES|SUMMARIES|TEXT|TITLES|TOOLTIPS|WARNINGS)$/
|
|
const MOBILE_USER_VISIBLE_CALL_ARGUMENTS = new Map([
|
|
['assertRpcOk', new Set([1])],
|
|
['browserErrorMessage', new Set([1])],
|
|
['failureState', new Set([0])],
|
|
['onError', new Set([0])],
|
|
['onFailure', new Set([0])],
|
|
['onSendError', new Set([0])],
|
|
['onToast', new Set([0])],
|
|
['previewError', new Set([0])],
|
|
['reportEngineError', new Set([0])],
|
|
['reportNativeEngineError', new Set([0])],
|
|
['sendMobileHostedReviewGitMutation', new Set([3])]
|
|
])
|
|
const USER_VISIBLE_FUNCTION_NAMES = new Set([
|
|
'alert',
|
|
'confirm',
|
|
'prompt',
|
|
'showError',
|
|
'showToast'
|
|
])
|
|
const USER_VISIBLE_OBJECT_METHODS = new Set([
|
|
'error',
|
|
'info',
|
|
'loading',
|
|
'message',
|
|
'promise',
|
|
'success',
|
|
'warning'
|
|
])
|
|
const USER_VISIBLE_OBJECT_NAMES = new Set(['toast'])
|
|
const USER_VISIBLE_VARIABLE_NAMES = new Set([
|
|
'caption',
|
|
'description',
|
|
'detail',
|
|
'emptyLabel',
|
|
'error',
|
|
'explanation',
|
|
'heading',
|
|
'hint',
|
|
'label',
|
|
'message',
|
|
'notice',
|
|
'placeholder',
|
|
'statusText',
|
|
'subtitle',
|
|
'summary',
|
|
'text',
|
|
'title',
|
|
'warning'
|
|
])
|
|
const USER_VISIBLE_VARIABLE_SUFFIX_RE =
|
|
/(?:Caption|Description|EmptyLabel|Error|Explanation|Heading|Hint|Label|Message|Notice|Placeholder|StatusText|Subtitle|Summary|Title|Warning)$|_(?:CAPTION|DESCRIPTION|EMPTY_LABEL|ERROR|EXPLANATION|HEADING|HINT|LABEL|MESSAGE|NOTICE|PLACEHOLDER|STATUS_TEXT|SUBTITLE|SUMMARY|TITLE|WARNING)$/
|
|
const USER_VISIBLE_RETURN_FUNCTIONS = new Set([
|
|
'accessibilityLabelForLine',
|
|
'autoRestoreSummary',
|
|
'commentAuthor',
|
|
'commentSourceLabel',
|
|
'composerLabel',
|
|
'describeScope',
|
|
'discussionSummary',
|
|
'formatAgo',
|
|
'formatBranchLabel',
|
|
'formatCommitTime',
|
|
'formatDurationSeconds',
|
|
'formatMobileHostedReviewCreateError',
|
|
'formatPrCommentRelativeTime',
|
|
'formatTimeAgo',
|
|
'formatUpdatedAt',
|
|
'getGitHubChecksLabel',
|
|
'getGitHubMergeLabel',
|
|
'getHostedMergeConfirmMessage',
|
|
'getHostedReviewMergeMethodLabel',
|
|
'getHostedStateConfirmLabel',
|
|
'getHostedStateConfirmMessage',
|
|
'getHostedStateConfirmTitle',
|
|
'getMobileSessionTabTitle',
|
|
'getProjectGitHubMergeConfirmMessage',
|
|
'getPrComposeDisabledReason',
|
|
'getWorktreeLabel',
|
|
'gitHubStatusLabel',
|
|
'gitLabStatusLabel',
|
|
'gitLabTodoTargetLabel',
|
|
'hostedReviewMergeTargetLabel',
|
|
'labelForEmpty',
|
|
'mobileAiVaultResumeTargetBlockMessage',
|
|
'mobileConflictAbortLabel',
|
|
'mobileConnectionPathLabel',
|
|
'mobileReviewScopeLabel',
|
|
'projectFieldValueLabel',
|
|
'projectRowStatusLabel',
|
|
'repositoryCount',
|
|
'saveErrorMessageFromPreviewResult',
|
|
'sendSheetMessage',
|
|
'setupSourceLabel',
|
|
'taskExternalOpenLabel',
|
|
'taskKindLabel',
|
|
'taskStatusActionLabel',
|
|
'workspaceAgentLabel',
|
|
'workspaceSshStatusLabel'
|
|
])
|
|
|
|
function propertyNameText(name) {
|
|
if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) {
|
|
return name.text
|
|
}
|
|
if (ts.isComputedPropertyName(name) && ts.isStringLiteralLike(name.expression)) {
|
|
return name.expression.text
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function expressionNameText(node) {
|
|
if (ts.isIdentifier(node)) {
|
|
return node.text
|
|
}
|
|
if (ts.isPropertyAccessExpression(node)) {
|
|
return `${expressionNameText(node.expression) ?? ''}.${node.name.text}`.replace(/^\./, '')
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function findAncestor(node, predicate) {
|
|
let current = node.parent
|
|
while (current) {
|
|
if (predicate(current)) {
|
|
return current
|
|
}
|
|
current = current.parent
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function isJsxAttributeValue(node) {
|
|
const parent = node.parent
|
|
if (!parent) {
|
|
return undefined
|
|
}
|
|
if (ts.isJsxAttribute(parent)) {
|
|
return propertyNameText(parent.name)
|
|
}
|
|
if (parent && ts.isJsxExpression(parent) && parent.parent && ts.isJsxAttribute(parent.parent)) {
|
|
return propertyNameText(parent.parent.name)
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function ancestorJsxAttributeName(node) {
|
|
let current = node.parent
|
|
while (current) {
|
|
if (ts.isJsxAttribute(current)) {
|
|
return propertyNameText(current.name)
|
|
}
|
|
if (
|
|
ts.isJsxExpression(current) ||
|
|
ts.isConditionalExpression(current) ||
|
|
ts.isParenthesizedExpression(current) ||
|
|
ts.isBinaryExpression(current)
|
|
) {
|
|
current = current.parent
|
|
continue
|
|
}
|
|
return undefined
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function nearestObjectPropertyName(node) {
|
|
let current = node.parent
|
|
while (current) {
|
|
if (ts.isPropertyAssignment(current) || ts.isShorthandPropertyAssignment(current)) {
|
|
return propertyNameText(current.name)
|
|
}
|
|
if (ts.isObjectLiteralExpression(current) || ts.isArrayLiteralExpression(current)) {
|
|
current = current.parent
|
|
continue
|
|
}
|
|
return undefined
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function nearestAncestorObjectPropertyName(node) {
|
|
let current = node.parent
|
|
while (current) {
|
|
if (ts.isPropertyAssignment(current) || ts.isShorthandPropertyAssignment(current)) {
|
|
return propertyNameText(current.name)
|
|
}
|
|
current = current.parent
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function hasAncestorObjectPropertyName(node, names) {
|
|
let current = node.parent
|
|
while (current) {
|
|
if (
|
|
(ts.isPropertyAssignment(current) || ts.isShorthandPropertyAssignment(current)) &&
|
|
names.has(propertyNameText(current.name) ?? '')
|
|
) {
|
|
return true
|
|
}
|
|
current = current.parent
|
|
}
|
|
return false
|
|
}
|
|
|
|
function isDirectDisplayExpressionParent(parent, child) {
|
|
if (
|
|
ts.isParenthesizedExpression(parent) ||
|
|
ts.isAsExpression(parent) ||
|
|
ts.isSatisfiesExpression(parent)
|
|
) {
|
|
return parent.expression === child
|
|
}
|
|
if (ts.isConditionalExpression(parent)) {
|
|
return parent.whenTrue === child || parent.whenFalse === child
|
|
}
|
|
if (ts.isBinaryExpression(parent)) {
|
|
return [
|
|
ts.SyntaxKind.PlusToken,
|
|
ts.SyntaxKind.BarBarToken,
|
|
ts.SyntaxKind.QuestionQuestionToken
|
|
].includes(parent.operatorToken.kind)
|
|
}
|
|
return ts.isTemplateSpan(parent) || ts.isTemplateExpression(parent)
|
|
}
|
|
|
|
function directDisplayVariableName(node) {
|
|
let current = node
|
|
while (current.parent && isDirectDisplayExpressionParent(current.parent, current)) {
|
|
current = current.parent
|
|
}
|
|
const declaration = current.parent
|
|
if (
|
|
declaration &&
|
|
(ts.isVariableDeclaration(declaration) ||
|
|
ts.isBindingElement(declaration) ||
|
|
ts.isParameter(declaration)) &&
|
|
declaration.initializer === current &&
|
|
ts.isIdentifier(declaration.name)
|
|
) {
|
|
return declaration.name.text
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function isUserVisibleVariableValue(node) {
|
|
const name = directDisplayVariableName(node)
|
|
return Boolean(
|
|
name && (USER_VISIBLE_VARIABLE_NAMES.has(name) || USER_VISIBLE_VARIABLE_SUFFIX_RE.test(name))
|
|
)
|
|
}
|
|
|
|
function directDisplayCollectionName(node) {
|
|
let current = node
|
|
while (current.parent) {
|
|
if (isDirectDisplayExpressionParent(current.parent, current)) {
|
|
current = current.parent
|
|
continue
|
|
}
|
|
if (
|
|
(ts.isPropertyAssignment(current.parent) && current.parent.initializer === current) ||
|
|
ts.isObjectLiteralExpression(current.parent) ||
|
|
ts.isArrayLiteralExpression(current.parent)
|
|
) {
|
|
current = current.parent
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
const declaration = current.parent
|
|
return declaration &&
|
|
ts.isVariableDeclaration(declaration) &&
|
|
declaration.initializer === current &&
|
|
ts.isIdentifier(declaration.name)
|
|
? declaration.name.text
|
|
: undefined
|
|
}
|
|
|
|
function isDisplayFormatterArgument(node) {
|
|
const call = findAncestor(node, ts.isCallExpression)
|
|
const name = call ? expressionNameText(call.expression)?.split('.').at(-1) : undefined
|
|
return Boolean(name && USER_VISIBLE_RETURN_FUNCTIONS.has(name))
|
|
}
|
|
|
|
function directCallArgumentIndex(call, node) {
|
|
let current = node
|
|
while (current.parent && current.parent !== call) {
|
|
if (!isDirectDisplayExpressionParent(current.parent, current)) {
|
|
return -1
|
|
}
|
|
current = current.parent
|
|
}
|
|
return call.arguments.indexOf(current)
|
|
}
|
|
|
|
function isUserVisibleCallArgument(node, userVisibleErrorSource) {
|
|
const call = findAncestor(node, ts.isCallExpression)
|
|
const expressionName = call ? expressionNameText(call.expression) : undefined
|
|
if (!call || !expressionName) {
|
|
return false
|
|
}
|
|
const parts = expressionName.split('.')
|
|
const methodName = parts.at(-1) ?? ''
|
|
const objectName = parts.at(-2)
|
|
const mobileArgumentIndexes = MOBILE_USER_VISIBLE_CALL_ARGUMENTS.get(methodName)
|
|
return (
|
|
(userVisibleErrorSource && expressionName === 'Error') ||
|
|
mobileArgumentIndexes?.has(directCallArgumentIndex(call, node)) ||
|
|
/^set[A-Za-z0-9]*(Error|Message|Notice|Warning)$/.test(methodName) ||
|
|
USER_VISIBLE_FUNCTION_NAMES.has(expressionName) ||
|
|
USER_VISIBLE_FUNCTION_NAMES.has(methodName) ||
|
|
(objectName !== undefined &&
|
|
USER_VISIBLE_OBJECT_NAMES.has(objectName) &&
|
|
USER_VISIBLE_OBJECT_METHODS.has(methodName))
|
|
)
|
|
}
|
|
|
|
function isUserVisibleErrorArgument(node, userVisibleErrorSource) {
|
|
if (!userVisibleErrorSource) {
|
|
return false
|
|
}
|
|
const expression = findAncestor(node, ts.isNewExpression)
|
|
return Boolean(
|
|
expression && ts.isIdentifier(expression.expression) && expression.expression.text === 'Error'
|
|
)
|
|
}
|
|
|
|
function resolveBoundInitializer(node, bindings, seen = new Set()) {
|
|
if (
|
|
ts.isParenthesizedExpression(node) ||
|
|
ts.isAsExpression(node) ||
|
|
ts.isSatisfiesExpression(node)
|
|
) {
|
|
return resolveBoundInitializer(node.expression, bindings, seen)
|
|
}
|
|
if (!bindings || !ts.isIdentifier(node)) {
|
|
return node
|
|
}
|
|
const binding = bindings.resolveBinding(node)
|
|
if (!binding || seen.has(binding) || !ts.isVariableDeclaration(binding) || !binding.initializer) {
|
|
return node
|
|
}
|
|
seen.add(binding)
|
|
return resolveBoundInitializer(binding.initializer, bindings, seen)
|
|
}
|
|
|
|
function objectPropertyValues(node, propertyName, bindings, seen = new Set()) {
|
|
const expression = resolveBoundInitializer(node, bindings, seen)
|
|
if (!ts.isObjectLiteralExpression(expression)) {
|
|
return []
|
|
}
|
|
return expression.properties.flatMap((property) => {
|
|
if (ts.isPropertyAssignment(property) && propertyNameText(property.name) === propertyName) {
|
|
return [property.initializer]
|
|
}
|
|
if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) {
|
|
return [property.name]
|
|
}
|
|
return ts.isSpreadAssignment(property)
|
|
? objectPropertyValues(property.expression, propertyName, bindings, new Set(seen))
|
|
: []
|
|
})
|
|
}
|
|
|
|
function collectBoundStringNodes(expression, bindings, nodes, seen = new Set()) {
|
|
const resolved = resolveBoundInitializer(expression, bindings, seen)
|
|
if (ts.isStringLiteralLike(resolved) || ts.isTemplateExpression(resolved)) {
|
|
nodes.add(resolved)
|
|
return
|
|
}
|
|
ts.forEachChild(resolved, (child) =>
|
|
collectBoundStringNodes(child, bindings, nodes, new Set(seen))
|
|
)
|
|
}
|
|
|
|
function isNotificationChannelName(node, bindings) {
|
|
const sourceFile = node.getSourceFile()
|
|
const cached = NOTIFICATION_CHANNEL_NAME_NODES.get(sourceFile)
|
|
if (cached) {
|
|
return cached.has(node)
|
|
}
|
|
const nodes = new Set()
|
|
function visit(current) {
|
|
if (
|
|
ts.isCallExpression(current) &&
|
|
current.arguments[1] &&
|
|
expressionNameText(current.expression)?.endsWith('setNotificationChannelAsync')
|
|
) {
|
|
for (const value of objectPropertyValues(current.arguments[1], 'name', bindings)) {
|
|
collectBoundStringNodes(value, bindings, nodes)
|
|
}
|
|
}
|
|
ts.forEachChild(current, visit)
|
|
}
|
|
visit(sourceFile)
|
|
NOTIFICATION_CHANNEL_NAME_NODES.set(sourceFile, nodes)
|
|
return nodes.has(node)
|
|
}
|
|
|
|
function isRenderedMobileExpression(expression) {
|
|
return isRenderedJsxExpression(expression) || isAssignedToRenderedVariable(expression)
|
|
}
|
|
|
|
function hasDisqualifyingBinaryAncestor(node) {
|
|
let current = node.parent
|
|
while (current) {
|
|
if (ts.isFunctionLike(current)) {
|
|
return false
|
|
}
|
|
if (
|
|
ts.isBinaryExpression(current) &&
|
|
![
|
|
ts.SyntaxKind.PlusToken,
|
|
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
ts.SyntaxKind.BarBarToken,
|
|
ts.SyntaxKind.QuestionQuestionToken
|
|
].includes(current.operatorToken.kind)
|
|
) {
|
|
return true
|
|
}
|
|
current = current.parent
|
|
}
|
|
return false
|
|
}
|
|
|
|
function isComparisonOperand(node) {
|
|
let current = node
|
|
while (
|
|
current.parent &&
|
|
(ts.isParenthesizedExpression(current.parent) ||
|
|
ts.isAsExpression(current.parent) ||
|
|
ts.isSatisfiesExpression(current.parent))
|
|
) {
|
|
current = current.parent
|
|
}
|
|
return Boolean(
|
|
current.parent &&
|
|
ts.isBinaryExpression(current.parent) &&
|
|
![
|
|
ts.SyntaxKind.PlusToken,
|
|
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
ts.SyntaxKind.BarBarToken,
|
|
ts.SyntaxKind.QuestionQuestionToken
|
|
].includes(current.parent.operatorToken.kind)
|
|
)
|
|
}
|
|
|
|
export function classifyMobileStringNode(node, userVisibleErrorSource, bindings) {
|
|
if (hasAncestorObjectPropertyName(node, new Set(['className', 'classNames']))) {
|
|
return undefined
|
|
}
|
|
if (
|
|
isAssignedToRenderedVariable(node, (expression) => {
|
|
if (isRenderedJsxExpression(expression)) {
|
|
return true
|
|
}
|
|
const attributeName = ancestorJsxAttributeName(expression)
|
|
return Boolean(
|
|
attributeName &&
|
|
(USER_VISIBLE_JSX_ATTRIBUTES.has(attributeName) ||
|
|
MOBILE_USER_VISIBLE_JSX_ATTRIBUTES.has(attributeName) ||
|
|
MOBILE_USER_VISIBLE_PROPERTY_SUFFIX_RE.test(attributeName))
|
|
)
|
|
})
|
|
) {
|
|
return 'rendered-variable'
|
|
}
|
|
if (isComparisonOperand(node)) {
|
|
return undefined
|
|
}
|
|
if (isNotificationChannelName(node, bindings)) {
|
|
return 'notification-channel-name'
|
|
}
|
|
|
|
const jsxAttributeName = isJsxAttributeValue(node)
|
|
if (jsxAttributeName) {
|
|
return USER_VISIBLE_JSX_ATTRIBUTES.has(jsxAttributeName) ||
|
|
MOBILE_USER_VISIBLE_JSX_ATTRIBUTES.has(jsxAttributeName) ||
|
|
MOBILE_USER_VISIBLE_PROPERTY_SUFFIX_RE.test(jsxAttributeName)
|
|
? `jsx-attribute:${jsxAttributeName}`
|
|
: undefined
|
|
}
|
|
const ancestorAttributeName = ancestorJsxAttributeName(node)
|
|
if (ancestorAttributeName) {
|
|
return USER_VISIBLE_JSX_ATTRIBUTES.has(ancestorAttributeName) ||
|
|
MOBILE_USER_VISIBLE_JSX_ATTRIBUTES.has(ancestorAttributeName) ||
|
|
MOBILE_USER_VISIBLE_PROPERTY_SUFFIX_RE.test(ancestorAttributeName)
|
|
? `jsx-attribute:${ancestorAttributeName}`
|
|
: undefined
|
|
}
|
|
if (ts.isJsxText(node)) {
|
|
return 'jsx-text'
|
|
}
|
|
|
|
const objectPropertyName = nearestObjectPropertyName(node)
|
|
const userVisibleObjectProperty =
|
|
objectPropertyName &&
|
|
(USER_VISIBLE_OBJECT_KEYS.has(objectPropertyName) ||
|
|
MOBILE_USER_VISIBLE_OBJECT_KEYS.has(objectPropertyName) ||
|
|
MOBILE_USER_VISIBLE_PROPERTY_SUFFIX_RE.test(objectPropertyName))
|
|
if (
|
|
isUserVisibleCallArgument(node, userVisibleErrorSource) &&
|
|
(!objectPropertyName || objectPropertyName === 'text' || userVisibleObjectProperty)
|
|
) {
|
|
return 'user-visible-call'
|
|
}
|
|
const collectionName = directDisplayCollectionName(node)
|
|
if (collectionName && USER_VISIBLE_COLLECTION_SUFFIX_RE.test(collectionName)) {
|
|
return 'user-visible-collection'
|
|
}
|
|
if (objectPropertyName && !userVisibleObjectProperty) {
|
|
return undefined
|
|
}
|
|
if (isDisplayFormatterArgument(node)) {
|
|
return undefined
|
|
}
|
|
|
|
const ancestorObjectPropertyName = nearestAncestorObjectPropertyName(node)
|
|
if (
|
|
ancestorObjectPropertyName &&
|
|
!USER_VISIBLE_OBJECT_KEYS.has(ancestorObjectPropertyName) &&
|
|
!MOBILE_USER_VISIBLE_OBJECT_KEYS.has(ancestorObjectPropertyName) &&
|
|
!MOBILE_USER_VISIBLE_PROPERTY_SUFFIX_RE.test(ancestorObjectPropertyName)
|
|
) {
|
|
return undefined
|
|
}
|
|
if (ancestorObjectPropertyName) {
|
|
return `object-property:${ancestorObjectPropertyName}`
|
|
}
|
|
if (hasDisqualifyingBinaryAncestor(node)) {
|
|
return undefined
|
|
}
|
|
if (isUserVisibleVariableValue(node)) {
|
|
return 'user-visible-variable'
|
|
}
|
|
const returnFunctionName = directReturnFunctionName(node)
|
|
if (
|
|
returnFunctionName &&
|
|
(USER_VISIBLE_RETURN_FUNCTIONS.has(returnFunctionName) ||
|
|
USER_VISIBLE_VARIABLE_SUFFIX_RE.test(returnFunctionName))
|
|
) {
|
|
return 'user-visible-return'
|
|
}
|
|
if (returnFunctionName && isReturnedByRenderedFunction(node, isRenderedMobileExpression)) {
|
|
return 'rendered-function-return'
|
|
}
|
|
if (isUserVisibleErrorArgument(node, userVisibleErrorSource)) {
|
|
return 'user-visible-error'
|
|
}
|
|
if (isRenderedJsxExpression(node)) {
|
|
return 'jsx-expression'
|
|
}
|
|
if (isUserVisibleCallArgument(node, userVisibleErrorSource)) {
|
|
return 'user-visible-call'
|
|
}
|
|
return objectPropertyName ? `object-property:${objectPropertyName}` : undefined
|
|
}
|