/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-dom-bindings/src/shared/ReactDOMUnknownPropertyHook.js
374 строки
12 KB
Jan Kassens
lint: enable reportUnusedDisableDirectives and remove unused suppressions (#28721)
21 июн 2024, 19:24
Не верифицирован
21 июн 2024, 19:24
b565373
Код
Авторство
О чём код?
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import {ATTRIBUTE_NAME_CHAR} from './isAttributeNameSafe'; import isCustomElement from './isCustomElement'; import possibleStandardNames from './possibleStandardNames'; import hasOwnProperty from 'shared/hasOwnProperty'; const warnedProperties = {}; const EVENT_NAME_REGEX = /^on./; const INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/; const rARIA = __DEV__ ? new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$') : null; const rARIACamel = __DEV__ ? new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$') : null; function validateProperty(tagName, name, value, eventRegistry) { if (__DEV__) { if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) { return true; } const lowerCasedName = name.toLowerCase(); if (lowerCasedName === 'onfocusin' || lowerCasedName === 'onfocusout') { console.error( 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' + 'All React events are normalized to bubble, so onFocusIn and onFocusOut ' + 'are not needed/supported by React.', ); warnedProperties[name] = true; return true; } // Actions are special because unlike events they can have other value types. if (typeof value === 'function') { if (tagName === 'form' && name === 'action') { return true; } if (tagName === 'input' && name === 'formAction') { return true; } if (tagName === 'button' && name === 'formAction') { return true; } } // We can't rely on the event system being injected on the server. if (eventRegistry != null) { const {registrationNameDependencies, possibleRegistrationNames} = eventRegistry; if (registrationNameDependencies.hasOwnProperty(name)) { return true; } const registrationName = possibleRegistrationNames.hasOwnProperty( lowerCasedName, ) ? possibleRegistrationNames[lowerCasedName] : null; if (registrationName != null) { console.error( 'Invalid event handler property `%s`. Did you mean `%s`?', name, registrationName, ); warnedProperties[name] = true; return true; } if (EVENT_NAME_REGEX.test(name)) { console.error( 'Unknown event handler property `%s`. It will be ignored.', name, ); warnedProperties[name] = true; return true; } } else if (EVENT_NAME_REGEX.test(name)) { // If no event plugins have been injected, we are in a server environment. // So we can't tell if the event name is correct for sure, but we can filter // out known bad ones like `onclick`. We can't suggest a specific replacement though. if (INVALID_EVENT_NAME_REGEX.test(name)) { console.error( 'Invalid event handler property `%s`. ' + 'React events use the camelCase naming convention, for example `onClick`.', name, ); } warnedProperties[name] = true; return true; } // Let the ARIA attribute hook validate ARIA attributes if (rARIA.test(name) || rARIACamel.test(name)) { return true; } if (lowerCasedName === 'innerhtml') { console.error( 'Directly setting property `innerHTML` is not permitted. ' + 'For more information, lookup documentation on `dangerouslySetInnerHTML`.', ); warnedProperties[name] = true; return true; } if (lowerCasedName === 'aria') { console.error( 'The `aria` attribute is reserved for future use in React. ' + 'Pass individual `aria-` attributes instead.', ); warnedProperties[name] = true; return true; } if ( lowerCasedName === 'is' && value !== null && value !== undefined && typeof value !== 'string' ) { console.error( 'Received a `%s` for a string attribute `is`. If this is expected, cast ' + 'the value to a string.', typeof value, ); warnedProperties[name] = true; return true; } if (typeof value === 'number' && isNaN(value)) { console.error( 'Received NaN for the `%s` attribute. If this is expected, cast ' + 'the value to a string.', name, ); warnedProperties[name] = true; return true; } // Known attributes should match the casing specified in the property config. if (possibleStandardNames.hasOwnProperty(lowerCasedName)) { const standardName = possibleStandardNames[lowerCasedName]; if (standardName !== name) { console.error( 'Invalid DOM property `%s`. Did you mean `%s`?', name, standardName, ); warnedProperties[name] = true; return true; } } else if (name !== lowerCasedName) { // Unknown attributes should have lowercase casing since that's how they // will be cased anyway with server rendering. console.error( 'React does not recognize the `%s` prop on a DOM element. If you ' + 'intentionally want it to appear in the DOM as a custom ' + 'attribute, spell it as lowercase `%s` instead. ' + 'If you accidentally passed it from a parent component, remove ' + 'it from the DOM element.', name, lowerCasedName, ); warnedProperties[name] = true; return true; } // Now that we've validated casing, do not validate // data types for reserved props switch (name) { case 'dangerouslySetInnerHTML': case 'children': case 'style': case 'suppressContentEditableWarning': case 'suppressHydrationWarning': case 'defaultValue': // Reserved case 'defaultChecked': case 'innerHTML': case 'ref': { return true; } case 'innerText': // Properties case 'textContent': return true; } switch (typeof value) { case 'boolean': { switch (name) { case 'autoFocus': case 'checked': case 'multiple': case 'muted': case 'selected': case 'contentEditable': case 'spellCheck': case 'draggable': case 'value': case 'autoReverse': case 'externalResourcesRequired': case 'focusable': case 'preserveAlpha': case 'allowFullScreen': case 'async': case 'autoPlay': case 'controls': case 'default': case 'defer': case 'disabled': case 'disablePictureInPicture': case 'disableRemotePlayback': case 'formNoValidate': case 'hidden': case 'loop': case 'noModule': case 'noValidate': case 'open': case 'playsInline': case 'readOnly': case 'required': case 'reversed': case 'scoped': case 'seamless': case 'itemScope': case 'capture': case 'download': case 'inert': { // Boolean properties can accept boolean values return true; } // fallthrough default: { const prefix = name.toLowerCase().slice(0, 5); if (prefix === 'data-' || prefix === 'aria-') { return true; } if (value) { console.error( 'Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.', value, name, name, value, name, ); } else { console.error( 'Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.\n\n' + 'If you used to conditionally omit it with %s={condition && value}, ' + 'pass %s={condition ? value : undefined} instead.', value, name, name, value, name, name, name, ); } warnedProperties[name] = true; return true; } } } case 'function': case 'symbol': // Warn when a known attribute is a bad type warnedProperties[name] = true; return false; case 'string': { // Warn when passing the strings 'false' or 'true' into a boolean prop if (value === 'false' || value === 'true') { switch (name) { case 'checked': case 'selected': case 'multiple': case 'muted': case 'allowFullScreen': case 'async': case 'autoPlay': case 'controls': case 'default': case 'defer': case 'disabled': case 'disablePictureInPicture': case 'disableRemotePlayback': case 'formNoValidate': case 'hidden': case 'loop': case 'noModule': case 'noValidate': case 'open': case 'playsInline': case 'readOnly': case 'required': case 'reversed': case 'scoped': case 'seamless': case 'itemScope': case 'inert': { break; } default: { return true; } } console.error( 'Received the string `%s` for the boolean attribute `%s`. ' + '%s ' + 'Did you mean %s={%s}?', value, name, value === 'false' ? 'The browser will interpret it as a truthy value.' : 'Although this works, it will not work as expected if you pass the string "false".', name, value, ); warnedProperties[name] = true; return true; } } } return true; } } function warnUnknownProperties(type, props, eventRegistry) { if (__DEV__) { const unknownProps = []; for (const key in props) { const isValid = validateProperty(type, key, props[key], eventRegistry); if (!isValid) { unknownProps.push(key); } } const unknownPropString = unknownProps .map(prop => '`' + prop + '`') .join(', '); if (unknownProps.length === 1) { console.error( 'Invalid value for prop %s on <%s> tag. Either remove it from the element, ' + 'or pass a string or number value to keep it in the DOM. ' + 'For details, see https://react.dev/link/attribute-behavior ', unknownPropString, type, ); } else if (unknownProps.length > 1) { console.error( 'Invalid values for props %s on <%s> tag. Either remove them from the element, ' + 'or pass a string or number value to keep them in the DOM. ' + 'For details, see https://react.dev/link/attribute-behavior ', unknownPropString, type, ); } } } export function validateProperties(type, props, eventRegistry) { if (isCustomElement(type, props) || typeof props.is === 'string') { return; } warnUnknownProperties(type, props, eventRegistry); }