/// Omit needless words from the beginning of a name. static StringRef omitNeedlessWordsFromPrefix(StringRef name, OmissionTypeName type, StringScratchSpace &scratch){ if (type.empty()) return name; // Match the result type to the beginning of the name. StringRef newName = matchLeadingTypeName(name, type); if (newName == name) return name; auto firstWord = camel_case::getFirstWord(newName); // If we have a preposition, we can chop off type information at the // beginning of the name. if (getPartOfSpeech(firstWord) == PartOfSpeech::Preposition && newName.size() > firstWord.size()) { // If the preposition was "by" and is followed by a gerund, also remove // "by". if (firstWord == "By") { StringRef nextWord = camel_case::getFirstWord( newName.substr(firstWord.size())); if (getPartOfSpeech(nextWord) == PartOfSpeech::Gerund) { return toLowercaseWord(newName.substr(firstWord.size()), scratch); } } return toLowercaseWord(newName, scratch); } return name; }
/// Split the base name, if it makes sense. static bool splitBaseName(StringRef &baseName, StringRef &argName, const OmissionTypeName ¶mType, StringRef paramName) { // If there is already an argument label, do nothing. if (!argName.empty()) return false; // Try splitting a Boolean "Animated". if (paramType.isBoolean() && camel_case::getLastWord(baseName) == "Animated") { baseName = baseName.substr(0, baseName.size() - strlen("Animated")); argName = "animated"; return true; } // Don't split anything that starts with "set". if (camel_case::getFirstWord(baseName) == "set") return false; // Don't split a method that looks like an action (with a "sender" // of type AnyObject). if (paramName == "sender" && camel_case::getLastWord(paramType.Name) == "Object") return false; // Try splitting after the last preposition. if (splitBaseNameAfterLastPreposition(baseName, argName, paramType)) return true; return false; }
/// Determine whether the preposition in a split is "vacuous", and /// should be removed. static bool isVacuousPreposition(StringRef beforePreposition, StringRef preposition, StringRef afterPreposition, const OmissionTypeName ¶mType) { // Only consider "with" or "using" to be potentially vacuous. if (!camel_case::sameWordIgnoreFirstCase(preposition, "with") && !camel_case::sameWordIgnoreFirstCase(preposition, "using")) return false; // If the preposition is "with", check for special cases. if (camel_case::sameWordIgnoreFirstCase(preposition, "with")) { // Some words following the preposition indicate that "with" is // not vacuous. auto following = camel_case::getFirstWord(afterPreposition); if (camel_case::sameWordIgnoreFirstCase(following, "coder") || camel_case::sameWordIgnoreFirstCase(following, "zone")) return false; // If the last word of the argument label looks like a past // participle (ends in "-ed"), the preposition is not vacuous. auto lastWord = camel_case::getLastWord(afterPreposition); if (lastWord.endswith("ed")) return false; if (camel_case::sameWordIgnoreFirstCase(following, "delegate") || camel_case::sameWordIgnoreFirstCase(following, "frame")) return true; } // If the parameter has a default argument, it's vacuous. if (paramType.hasDefaultArgument()) return true; // If the parameter is of function type, it's vacuous. if (paramType.isFunction()) return true; // If the first word of the name is a verb, the preposition is // likely vacuous. if (getPartOfSpeech(camel_case::getFirstWord(beforePreposition)) == PartOfSpeech::Verb) return true; return false; }
/// Determine whether the preposition in a split is "vacuous", and /// should be removed. static bool isVacuousPreposition(StringRef beforePreposition, StringRef preposition, StringRef afterPreposition, const OmissionTypeName ¶mType) { // Only consider "with" or "using" to be potentially vacuous. if (!camel_case::sameWordIgnoreFirstCase(preposition, "with") && !camel_case::sameWordIgnoreFirstCase(preposition, "using")) return false; // If the preposition is "with" followed by "zone", never consider // it vacuous. if (camel_case::sameWordIgnoreFirstCase(preposition, "with") && camel_case::sameWordIgnoreFirstCase( camel_case::getFirstWord(afterPreposition), "zone")) return false; // If the parameter has a default argument, it's vacuous. if (paramType.hasDefaultArgument()) return true; // If the parameter is of function type, it's vacuous. if (paramType.isFunction()) return true; return false; }
static StringRef omitNeedlessWords(StringRef name, OmissionTypeName typeName, NameRole role, const InheritedNameSet *allPropertyNames, StringScratchSpace &scratch) { // If we have no name or no type name, there is nothing to do. if (name.empty() || typeName.empty()) return name; // Get the camel-case words in the name and type name. auto nameWords = camel_case::getWords(name); auto typeWords = camel_case::getWords(typeName.Name); // Match the last words in the type name to the last words in the // name. auto nameWordRevIter = nameWords.rbegin(), nameWordRevIterBegin = nameWordRevIter, firstMatchingNameWordRevIter = nameWordRevIter, nameWordRevIterEnd = nameWords.rend(); auto typeWordRevIter = typeWords.rbegin(), typeWordRevIterEnd = typeWords.rend(); bool anyMatches = false; auto matched = [&] { if (anyMatches) return; anyMatches = true; firstMatchingNameWordRevIter = nameWordRevIter; }; while (nameWordRevIter != nameWordRevIterEnd && typeWordRevIter != typeWordRevIterEnd) { // If the names match, continue. auto nameWord = *nameWordRevIter; if (matchNameWordToTypeWord(nameWord, *typeWordRevIter)) { matched(); ++nameWordRevIter; ++typeWordRevIter; continue; } // Special case: "Indexes" and "Indices" in the name match // "IndexSet" in the type. if ((matchNameWordToTypeWord(nameWord, "Indexes") || matchNameWordToTypeWord(nameWord, "Indices")) && *typeWordRevIter == "Set") { auto nextTypeWordRevIter = typeWordRevIter; ++nextTypeWordRevIter; if (nextTypeWordRevIter != typeWordRevIterEnd && matchNameWordToTypeWord("Index", *nextTypeWordRevIter)) { matched(); ++nameWordRevIter; typeWordRevIter = nextTypeWordRevIter; ++typeWordRevIter; continue; } } // Special case: "Index" in the name matches "Int" or "Integer" in the type. if (matchNameWordToTypeWord(nameWord, "Index") && (matchNameWordToTypeWord("Int", *typeWordRevIter) || matchNameWordToTypeWord("Integer", *typeWordRevIter))) { matched(); ++nameWordRevIter; ++typeWordRevIter; continue; } // Special case: if the word in the name ends in 's', and we have // a collection element type, see if this is a plural. if (!typeName.CollectionElement.empty() && nameWord.size() > 2 && nameWord.back() == 's' && role != NameRole::BaseNameSelf) { // Check <element name>s. auto shortenedNameWord = name.substr(0, nameWordRevIter.base().getPosition()-1); auto newShortenedNameWord = omitNeedlessWords(shortenedNameWord, typeName.CollectionElement, NameRole::Partial, allPropertyNames, scratch); if (shortenedNameWord != newShortenedNameWord) { matched(); unsigned targetSize = newShortenedNameWord.size(); while (nameWordRevIter.base().getPosition() > targetSize) ++nameWordRevIter; continue; } } // If this is a skippable suffix, skip it and keep looking. if (nameWordRevIter == nameWordRevIterBegin) { if (auto withoutSuffix = skipTypeSuffix(typeName.Name)) { typeName.Name = *withoutSuffix; typeWords = camel_case::getWords(typeName.Name); typeWordRevIter = typeWords.rbegin(); typeWordRevIterEnd = typeWords.rend(); continue; } } // If we're matching the base name of a method against the type of // 'Self', and we haven't matched anything yet, skip over words in // the name. if (role == NameRole::BaseNameSelf && !anyMatches) { ++nameWordRevIter; continue; } break; } StringRef origName = name; // If we matched anything above, update the name appropriately. if (anyMatches) { // Handle complete name matches. if (nameWordRevIter == nameWordRevIterEnd) { // If we're doing a partial match, return the empty string. if (role == NameRole::Partial) return ""; // Leave the name alone. return name; } // Don't strip just "Error". if (nameWordRevIter != nameWordRevIterBegin) { auto nameWordPrev = std::prev(nameWordRevIter); if (nameWordPrev == nameWordRevIterBegin && *nameWordPrev == "Error") return name; } switch (role) { case NameRole::Property: // Always strip off type information. name = name.substr(0, nameWordRevIter.base().getPosition()); break; case NameRole::BaseNameSelf: switch (getPartOfSpeech(*nameWordRevIter)) { case PartOfSpeech::Verb: { // Splice together the parts before and after the matched // type. For example, if we matched "ViewController" in // "dismissViewControllerAnimated", stitch together // "dismissAnimated". SmallString<16> newName = name.substr(0, nameWordRevIter.base().getPosition()); newName += name.substr(firstMatchingNameWordRevIter.base().getPosition()); name = scratch.copyString(newName); break; } case PartOfSpeech::Preposition: case PartOfSpeech::Gerund: case PartOfSpeech::Unknown: return name; } break; case NameRole::BaseName: case NameRole::FirstParameter: case NameRole::Partial: case NameRole::SubsequentParameter: // Classify the part of speech of the word before the type // information we would strip off. switch (getPartOfSpeech(*nameWordRevIter)) { case PartOfSpeech::Preposition: if (role == NameRole::BaseName) { // Strip off the part of the name that is redundant with // type information, so long as there's something preceding the // preposition. if (std::next(nameWordRevIter) != nameWordRevIterEnd) name = name.substr(0, nameWordRevIter.base().getPosition()); break; } SWIFT_FALLTHROUGH; case PartOfSpeech::Verb: case PartOfSpeech::Gerund: // Don't prune redundant type information from the base name if // there is a corresponding property (either singular or plural). if (allPropertyNames && role == NameRole::BaseName) { SmallString<16> localScratch; auto removedText = name.substr(nameWordRevIter.base().getPosition()); auto removedName = camel_case::toLowercaseWord(removedText, localScratch); // A property with exactly this name. if (allPropertyNames->contains(removedName)) return name; // From here on, we'll be working with scratch space. if (removedName.data() != localScratch.data()) localScratch = removedName; if (localScratch.back() == 'y') { // If the last letter is a 'y', try 'ies'. localScratch.pop_back(); localScratch += "ies"; if (allPropertyNames->contains(localScratch)) return name; } else { // Otherwise, add an 's' and try again. localScratch += 's'; if (allPropertyNames->contains(localScratch)) return name; // Alternatively, try to add 'es'. localScratch.pop_back(); localScratch += "es"; if (allPropertyNames->contains(localScratch)) return name; } } // Strip off the part of the name that is redundant with // type information. name = name.substr(0, nameWordRevIter.base().getPosition()); break; case PartOfSpeech::Unknown: // Assume it's a noun or adjective; don't strip anything. break; } break; } } // If we ended up with a vacuous name like "get" or "set", do nothing. if (isVacuousName(name)) return origName; switch (role) { case NameRole::BaseName: case NameRole::BaseNameSelf: case NameRole::Property: // If we ended up with a keyword for a property name or base name, // do nothing. if (isKeyword(name)) return origName; break; case NameRole::SubsequentParameter: case NameRole::FirstParameter: case NameRole::Partial: break; } // We're done. return name; }
bool swift::omitNeedlessWords(StringRef &baseName, MutableArrayRef<StringRef> argNames, StringRef firstParamName, OmissionTypeName resultType, OmissionTypeName contextType, ArrayRef<OmissionTypeName> paramTypes, bool returnsSelf, bool isProperty, const InheritedNameSet *allPropertyNames, StringScratchSpace &scratch) { bool anyChanges = false; /// Local function that lowercases all of the base names and /// argument names before returning. auto lowercaseAcronymsForReturn = [&] { StringRef newBaseName = toLowercaseWordAndAcronym(baseName, scratch); if (baseName.data() != newBaseName.data()) { baseName = newBaseName; anyChanges = true; } for (StringRef &argName : argNames) { StringRef newArgName = toLowercaseWordAndAcronym(argName, scratch); if (argName.data() != newArgName.data()) { argName = newArgName; anyChanges = true; } } return anyChanges; }; // If the result type matches the context, remove the context type from the // prefix of the name. bool resultTypeMatchesContext = returnsSelf || (resultType == contextType); if (resultTypeMatchesContext) { StringRef newBaseName = omitNeedlessWordsFromPrefix(baseName, contextType, scratch); if (newBaseName != baseName) { baseName = newBaseName; anyChanges = true; } } // Treat zero-parameter methods and properties the same way. if (paramTypes.empty()) { if (resultTypeMatchesContext) { StringRef newBaseName = ::omitNeedlessWords( baseName, returnsSelf ? contextType : resultType, NameRole::Property, allPropertyNames, scratch); if (newBaseName != baseName) { baseName = newBaseName; anyChanges = true; } } // Boolean properties should start with "is", unless their // first word already implies a Boolean result. if (resultType.isBoolean() && isProperty && !nameIndicatesBooleanResult(baseName)) { SmallString<32> newName("is"); camel_case::appendSentenceCase(newName, baseName); baseName = scratch.copyString(newName); anyChanges = true; } return lowercaseAcronymsForReturn(); } // Omit needless words based on parameter types. for (unsigned i = 0, n = argNames.size(); i != n; ++i) { // If there is no corresponding parameter, there is nothing to // omit. if (i >= paramTypes.size()) continue; // Omit needless words based on the type of the parameter. NameRole role = i > 0 ? NameRole::SubsequentParameter : argNames[0].empty() ? NameRole::BaseName : NameRole::FirstParameter; // Omit needless words from the name. StringRef name = role == NameRole::BaseName ? baseName : argNames[i]; StringRef newName = ::omitNeedlessWords(name, paramTypes[i], role, role == NameRole::BaseName ? allPropertyNames : nullptr, scratch); // Did the name change? if (name != newName) anyChanges = true; // If the first parameter has a default argument, and there is a // preposition in the base name, split the base name at that preposition. if (role == NameRole::BaseName && argNames[0].empty() && paramTypes[0].hasDefaultArgument()) { // Scan backwards for a preposition. auto nameWords = camel_case::getWords(newName); auto nameWordRevIter = nameWords.rbegin(), nameWordRevIterEnd = nameWords.rend(); bool found = false, done = false; while (nameWordRevIter != nameWordRevIterEnd && !done) { switch (getPartOfSpeech(*nameWordRevIter)) { case PartOfSpeech::Preposition: found = true; done = true; break; case PartOfSpeech::Verb: case PartOfSpeech::Gerund: // Don't skip over verbs or gerunds. done = true; break; case PartOfSpeech::Unknown: case PartOfSpeech::AuxiliaryVerb: ++nameWordRevIter; break; } } // If we found a split point that's not at the beginning of the // name, split there. if (found) { ++nameWordRevIter; unsigned splitPos = nameWordRevIter.base().getPosition(); if (splitPos > 0) { unsigned afterSplitPos = splitPos; // Create a first argument name with the remainder of the base name, // lowercased. If we would end up with a vacuous name, go // back and get the original. StringRef newArgName = newName.substr(afterSplitPos); if (isVacuousName(newArgName)) { size_t pos = name.rfind(newArgName); newArgName = name.substr(pos); } // If there is a leading "with" on the first argument, drop it. if (newArgName.size() > 4 && camel_case::sameWordIgnoreFirstCase( camel_case::getFirstWord(newArgName), "with")) { newArgName = newArgName.substr(4); } argNames[0] = toLowercaseWord(newArgName, scratch); // Update the base name by splitting at the preposition. newName = newName.substr(0, splitPos); anyChanges = true; } } } if (name == newName) continue; // Record this change. if (role == NameRole::BaseName) { baseName = newName; } else { argNames[i] = newName; } } return lowercaseAcronymsForReturn(); }