From b652137e96a7231c2d1e1c2e0c5d0db70f6b393c Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 28 Mar 2017 15:29:26 +0200 Subject: [PATCH] FEATURE: Provide code to handle deprecated method calls * Add parsing of YAML-files. * Check matching deprecated calls. * Provide necessary information for PHPCS and user. Relates: #33 --- .../Deprecated/Functions/7.0.yaml | 169 ++++++++--------- .../Deprecated/GenericFunctionCallSniff.php | 170 ++++++++++++++---- 2 files changed, 205 insertions(+), 134 deletions(-) diff --git a/src/Standards/Typo3Update/Configuration/Deprecated/Functions/7.0.yaml b/src/Standards/Typo3Update/Configuration/Deprecated/Functions/7.0.yaml index f01183a..4a56a00 100644 --- a/src/Standards/Typo3Update/Configuration/Deprecated/Functions/7.0.yaml +++ b/src/Standards/Typo3Update/Configuration/Deprecated/Functions/7.0.yaml @@ -1,114 +1,85 @@ # Deprecated in 7.0: https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Index.html#breaking-changes -loadTCA: - oldFunctionCall: '\TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA()' +\TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA: newFunctionCall: null docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61785-LoadTcaFunctionRemoved.html' -getCompressedTCarray: - oldFunctionCall: '\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::getCompressedTCarray()' +\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getCompressedTCarray: newFunctionCall: 'Full TCA is always loaded during bootstrap in FE, the method is obsolete. If an eid script calls this method to load TCA, use \TYPO3\CMS\Frontend\Utility\EidUtility::initTCA() instead' docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61785-FrontendTcaFunctionsRemoved.html' -includeTCA: - oldFunctionCall: '\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::includeTCA()' +\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->includeTCA: newFunctionCall: 'Full TCA is always loaded during bootstrap in FE, the method is obsolete. If an eid script calls this method to load TCA, use \TYPO3\CMS\Frontend\Utility\EidUtility::initTCA() instead' docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61785-FrontendTcaFunctionsRemoved.html' -mail: - oldFunctionCall: 'MailUtility::mail()' +\TYPO3\CMS\Core\Utility\MailUtility::mail: newFunctionCall: 'Use the \TYPO3\CMS\Core\Mail\Mailer API for sending email' docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61783-RemoveDeprecatedMailFunctionality.html' -plainMailEncoded: - oldFunctionCall: 'GeneralUtility::plainMailEncoded()' +\TYPO3\CMS\Core\Utility\GeneralUtility::plainMailEncoded: newFunctionCall: 'Use the \TYPO3\CMS\Core\Mail\Mailer API for sending email' docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61783-RemoveDeprecatedMailFunctionality.html' -connectDB: - oldFunctionCall: '\TYPO3\CMS\Frontend\Utility\EidUtility::connectDB()' +\TYPO3\CMS\Frontend\Utility\EidUtility::connectDB: newFunctionCall: null docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61863-ConnectDbFunctionRemoved.html' -int_from_ver: - oldFunctionCall: '\TYPO3\CMS\Core\Utility\GeneralUtility::int_from_ver' - newFunctionCall: 'Replace the usage of the removed function with \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger()' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61860-RemoveIntFromVerFunction.html' -getUniqueFields: - oldFunctionCall: '\TYPO3\CMS\Core\DataHandling\DataHandler::getUniqueFields()' - newFunctionCall: 'Replace all calls to \TYPO3\CMS\Core\DataHandling\DataHandler::getUniqueFields() with calls to \TYPO3\CMS\Version\Hook\DataHandlerHook::getUniqueFields()' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61822-GetUniqueFieldsFunctionRemoved.html' -isSafeModeEnabled: - oldFunctionCall: '\TYPO3\CMS\Core\Utility\PhpOptionsUtility::isSafeModeEnabled()' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61820-PhpOptionsUtilityDeprecatedFunctionsRemoved.html' -isMagicQuotesGpcEnabled: - oldFunctionCall: '\TYPO3\CMS\Core\Utility\PhpOptionsUtility::isMagicQuotesGpcEnabled()' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61820-PhpOptionsUtilityDeprecatedFunctionsRemoved.html' -isLocalconfWritable: - oldFunctionCall: '\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLocalconfWritable' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61802-IsLocalconfWritableFunctionRemoved.html' -create: - oldFunctionCall: 'ObjectManager::create()' - newFunctionCall: 'Use ObjectManager::get() instead' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' -# TODO: Find a way to make same method name in different classes work. -# create: -# oldFunctionCall: 'ObjectManagerInterface::create()' +# int_from_ver: +# newFunctionCall: 'Replace the usage of the removed function with \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger()' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61860-RemoveIntFromVerFunction.html' +# getUniqueFields: +# newFunctionCall: 'Replace all calls to \TYPO3\CMS\Core\DataHandling\DataHandler::getUniqueFields() with calls to \TYPO3\CMS\Version\Hook\DataHandlerHook::getUniqueFields()' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61822-GetUniqueFieldsFunctionRemoved.html' +# isSafeModeEnabled: # newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61820-PhpOptionsUtilityDeprecatedFunctionsRemoved.html' +# isMagicQuotesGpcEnabled: +# newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61820-PhpOptionsUtilityDeprecatedFunctionsRemoved.html' +# isLocalconfWritable: +# newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-61802-IsLocalconfWritableFunctionRemoved.html' +# create: +# newFunctionCall: 'Use ObjectManager::get() instead' # docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' -replaceObject: - oldFunctionCall: 'PersistenceGenericBackend::replaceObject()' - newFunctionCall: 'Removed without replacement' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' -setReturnRawQueryResult: - oldFunctionCall: 'QuerySettingsInterface::setReturnRawQueryResult()' - newFunctionCall: 'Removed without replacement' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' -getReturnRawQueryResult: - oldFunctionCall: 'QuerySettingsInterface::getReturnRawQueryResult()' - newFunctionCall: 'Use the parameter on $query->execute() directly' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' -setSysLanguageUid: - oldFunctionCall: 'Typo3QuerySettings::setSysLanguageUid()' - newFunctionCall: 'Use setLanguageUid() instead' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' -getSysLanguageUid: - oldFunctionCall: 'Typo3QuerySettings::getSysLanguageUid()' - newFunctionCall: 'Use getLanguageUid() instead' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' -JScharCode: - oldFunctionCall: 'LanguageService::JScharCode()' - newFunctionCall: 'Use GeneralUtility::quoteJSvalue instead' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -joinTSarrays: - oldFunctionCall: 'ContentObjectRenderer::joinTSarrays()' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -tidyHTML: - oldFunctionCall: 'TypoScriptFrontendController::tidyHTML()' - newFunctionCall: 'You may use the tidy extension from TER' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -isWebFolder: - oldFunctionCall: 'ElementBrowser::isWebFolder()' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -checkFolder: - oldFunctionCall: 'ElementBrowser::checkFolder()' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -getTreeObject: - oldFunctionCall: 'AbstractDatabaseRecordList::getTreeObject()' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -dirData: - oldFunctionCall: 'FileList::dirData()' - newFunctionCall: null - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -stdWrapValue: - oldFunctionCall: 'FilesContentObject::stdWrapValue()' - newFunctionCall: 'Use ContentObjectRenderer::stdWrapValue instead' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -userTempFolder: - oldFunctionCall: 'ImportExportController::userTempFolder()' - newFunctionCall: 'Use getDefaultImportExportFolder instead' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' -userSaveFolder: - oldFunctionCall: 'ImportExportController::userSaveFolder()' - newFunctionCall: 'Use getDefaultImportExportFolder instead' - docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# # create: +# # newFunctionCall: null +# # docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' +# replaceObject: +# newFunctionCall: 'Removed without replacement' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' +# setReturnRawQueryResult: +# newFunctionCall: 'Removed without replacement' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' +# getReturnRawQueryResult: +# newFunctionCall: 'Use the parameter on $query->execute() directly' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' +# setSysLanguageUid: +# newFunctionCall: 'Use setLanguageUid() instead' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' +# getSysLanguageUid: +# newFunctionCall: 'Use getLanguageUid() instead' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62673-ExtbaseDeprecatedCodeRemoved.html' +# JScharCode: +# newFunctionCall: 'Use GeneralUtility::quoteJSvalue instead' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# joinTSarrays: +# newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# tidyHTML: +# newFunctionCall: 'You may use the tidy extension from TER' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# isWebFolder: +# newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# checkFolder: +# newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# getTreeObject: +# newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# dirData: +# newFunctionCall: null +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# stdWrapValue: +# newFunctionCall: 'Use ContentObjectRenderer::stdWrapValue instead' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# userTempFolder: +# newFunctionCall: 'Use getDefaultImportExportFolder instead' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' +# userSaveFolder: +# newFunctionCall: 'Use getDefaultImportExportFolder instead' +# docsUrl: 'https://docs.typo3.org/typo3cms/extensions/core/Changelog/7.0/Breaking-62670-DeprecatedCodeRemovalInMultipleSysexts.html' diff --git a/src/Standards/Typo3Update/Sniffs/Deprecated/GenericFunctionCallSniff.php b/src/Standards/Typo3Update/Sniffs/Deprecated/GenericFunctionCallSniff.php index ff02b8d..895332f 100644 --- a/src/Standards/Typo3Update/Sniffs/Deprecated/GenericFunctionCallSniff.php +++ b/src/Standards/Typo3Update/Sniffs/Deprecated/GenericFunctionCallSniff.php @@ -37,27 +37,59 @@ class Typo3Update_Sniffs_Deprecated_GenericFunctionCallSniff implements PhpCsSni /** * Configuration to define deprecated functions. * - * Keys have to match the function name. - * - * TODO: Multiple files allowed, using glob ... to allow splitting per ext (extbase, fluid, ...) and TYPO3 Version 7.1, 7.0, ... - * TODO: Build necessary structure from that files, to make it more independent ... ?! - * * @var array */ protected static $deprecatedFunctions = []; + /** + * Function for the current sniff instance. + * @var array + */ + private $deprecatedFunction = []; + + /** + * TODO: Multiple files allowed, using glob ... to allow splitting per ext (extbase, fluid, ...) and TYPO3 Version 7.1, 7.0, ... + */ public function __construct() { if (static::$deprecatedFunctions === []) { foreach ($this->getDeprecatedFunctionConfigFiles() as $file) { static::$deprecatedFunctions = array_merge( static::$deprecatedFunctions, - Yaml::parse(file_get_contents((string) $file)) + $this->prepareStructure(Yaml::parse(file_get_contents((string) $file))) ); } } } + /** + * Prepares structure from config for later usage. + * + * @param array $deprecatedFunctions + * @return array + */ + protected function prepareStructure(array $deprecatedFunctions) + { + array_walk($deprecatedFunctions, function (&$config, $function) { + // Split static methods and methods. + $split = preg_split('/::|->/', $function); + + $config['static'] = strpos($function, '::') !== false; + $config['fqcn'] = null; + $config['class'] = null; + $config['function'] = $split[0]; + + // If split contains two parts, it's a class with method + if (isset($split[1])) { + $config['fqcn'] = $split[0]; + $config['class'] = array_slice(explode('\\', $config['fqcn']), -1)[0]; + $config['function'] = $split[1]; + } + }); + + return $deprecatedFunctions; + } + /** * Returns the token types that this sniff is interested in. * @@ -83,25 +115,92 @@ class Typo3Update_Sniffs_Deprecated_GenericFunctionCallSniff implements PhpCsSni */ public function process(PhpCsFile $phpcsFile, $stackPtr) { - if (!$this->isFunctionCall($phpcsFile, $stackPtr)) { + if (!$this->isFunctionCallDeprecated($phpcsFile, $stackPtr)) { return; } + $this->addWarning($phpcsFile, $stackPtr); + } + + /** + * Check whether function at given point is deprecated. + * + * @return bool + */ + protected function isFunctionCallDeprecated(PhpCsFile $phpcsFile, $stackPtr) + { + if (!$this->isFunctionCall($phpcsFile, $stackPtr)) { + return false; + } + $tokens = $phpcsFile->getTokens(); - $token = $tokens[$stackPtr]; + $staticPosition = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true, null, true); - if (in_array($token['content'], $this->getFunctionNames()) === false) { - return; + $functionName = $tokens[$stackPtr]['content']; + $isStatic = false; + $class = false; + + if ($staticPosition !== false) { + $isStatic = $tokens[$staticPosition]['code'] === T_DOUBLE_COLON; } - // TODO: Check if function is static and whether last class name part matches. - // TODO: Add new property to array "last class name part" and use for check if exists. - // TODO: How to handle methods? They are not static, are behind a variable or something else ... + if ($isStatic) { + $class = $phpcsFile->findPrevious(T_STRING, $staticPosition, null, false, null, true); + if ($class !== false) { + $class = $tokens[$class]['content']; + } + } - // E.g.: getUniqueFields - // E.g.: mail + return $this->getDeprecatedFunction($functionName, $class, $isStatic) !== []; + } - $this->addWarning($phpcsFile, $stackPtr); + /** + * Returns all matching deprecated functions for given arguments. + * + * Also prepares functions for later usages in $this->deprecatedFunction. + * + * @param string $functionName + * @param string $className The last part of the class name, splitted by namespaces. + * @param bool $isStatic + * + * @return array + */ + protected function getDeprecatedFunction($functionName, $className, $isStatic) + { + // We will not match any static method, without the class name, at least for now. + // Otherwise we could handle them the same way as instance methods. + if ($isStatic === true && $className === false) { + return []; + } + + $this->deprecatedFunction = array_filter( + static::$deprecatedFunctions, + function ($config) use ($functionName, $isStatic, $className) { + return $functionName === $config['function'] + && $isStatic === $config['static'] + && ( + $className === $config['class'] + || $className === false + ) // TODO: If no class, it's also fine, vor variable, non static methods. + ; + } + ); + + return $this->deprecatedFunction; + } + + /** + * Returns configuration for currently checked function. + * + * @return array + */ + protected function getCurrentDeprecatedFunction() + { + $config = current($this->deprecatedFunction); + + // TODO: Add exception if something went wrong? + + return $config; } /** @@ -114,31 +213,27 @@ class Typo3Update_Sniffs_Deprecated_GenericFunctionCallSniff implements PhpCsSni */ protected function addWarning(PhpCsFile $phpcsFile, $tokenPosition) { - $tokens = $phpcsFile->getTokens(); - $token = $tokens[$tokenPosition]; - $functionCall = $token['content']; - $phpcsFile->addWarning( 'Legacy function calls are not allowed; found %s. %s. See: %s', $tokenPosition, - $functionCall, + $this->getFunctionIdentifier(), [ - $this->getOldFunctionCall($functionCall), - $this->getNewFunctionCall($functionCall), - $this->getDocsUrl($functionCall), + $this->getOldfunctionCall(), + $this->getNewFunctionCall(), + $this->getDocsUrl(), ] ); } /** - * Provide all function names that are deprecated and should be handled by - * the Sniff. + * Identifier for configuring this specific error / warning through PHPCS. * - * @return array + * @return string */ - protected function getFunctionNames() + protected function getFunctionIdentifier() { - return array_keys(static::$deprecatedFunctions); + $config = $this->getCurrentDeprecatedFunction(); + return $config['class'] . '.' . $config['function']; } /** @@ -150,9 +245,14 @@ class Typo3Update_Sniffs_Deprecated_GenericFunctionCallSniff implements PhpCsSni * * @return string */ - protected function getOldFunctionCall($functionName) + protected function getOldFunctionCall() { - return static::$deprecatedFunctions[$functionName]['oldFunctionCall']; + $config = $this->getCurrentDeprecatedFunction(); + $concat = '->'; + if ($config['static']) { + $concat = '::'; + } + return $config['fqcn'] . $concat . $config['function']; } /** @@ -162,9 +262,9 @@ class Typo3Update_Sniffs_Deprecated_GenericFunctionCallSniff implements PhpCsSni * * @return string */ - protected function getNewFunctionCall($functionName) + protected function getNewFunctionCall() { - $newCall = static::$deprecatedFunctions[$functionName]['newFunctionCall']; + $newCall = $this->getCurrentDeprecatedFunction()['newFunctionCall']; if ($newCall !== null) { return $newCall; } @@ -176,8 +276,8 @@ class Typo3Update_Sniffs_Deprecated_GenericFunctionCallSniff implements PhpCsSni * * @return string */ - protected function getDocsUrl($functionName) + protected function getDocsUrl() { - return static::$deprecatedFunctions[$functionName]['docsUrl']; + return $this->getCurrentDeprecatedFunction()['docsUrl']; } }