├── .gitignore ├── .travis.yml ├── AbstractClassFoundation.podspec ├── AbstractClassFoundation.xcodeproj ├── AbstractClassFoundationTests_Info.plist ├── AbstractClassFoundation_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── AbstractClassFoundation-Package.xcscheme │ ├── AbstractClassFoundation.xcscheme │ └── AbstractClassFoundationTests.xcscheme ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cartfile ├── FOUNDATION_README.md ├── Images └── build_phases.jpg ├── LICENSE.txt ├── Makefile ├── NOTICE.txt ├── Package.swift ├── README.md ├── Sources └── AbstractClassFoundation │ └── AbstractDefinitions.swift ├── Tests └── AbstractClassFoundationTests │ └── AbstractDefinitionsTests.swift ├── Validator ├── .gitignore ├── Package.swift ├── README.md ├── Sources │ ├── AbstractClassValidatorFramework │ │ ├── Models │ │ │ ├── AbstractClassDefinition.swift │ │ │ ├── ConcreteSubclassDefinition.swift │ │ │ └── ValidationResult.swift │ │ ├── Processors │ │ │ ├── AbstractClassDefinitionsAggregator.swift │ │ │ └── ConcreteSubclassDefinitionsAggregator.swift │ │ ├── Tasks │ │ │ ├── BaseRegexUsageFilterTask.swift │ │ │ ├── ConcreteSubclassProducerTask.swift │ │ │ ├── ConcreteSubclassValidationTask.swift │ │ │ ├── DeclarationFilterTask.swift │ │ │ ├── DeclarationProducerTask.swift │ │ │ ├── ExpressionCallUsageFilterTask.swift │ │ │ ├── ExpressionCallValidationTask.swift │ │ │ ├── SubclassUsageFilterTask.swift │ │ │ └── TaskIds.swift │ │ ├── Utilities │ │ │ └── ASTUtils.swift │ │ └── Validator.swift │ └── abstractclassvalidator │ │ ├── ValidateCommand.swift │ │ ├── Version.swift │ │ ├── Version.swift.template │ │ ├── VersionCommand.swift │ │ └── main.swift ├── Tests │ └── AbstractClassValidatorFrameworkTests │ │ ├── AbstractClassDefinitionsAggregatorTests.swift │ │ ├── BaseFrameworkTests.swift │ │ ├── ConcreteSubclassDefinitionsAggregatorTests.swift │ │ ├── ConcreteSubclassProducerTaskTests.swift │ │ ├── ConcreteSubclassValidationTaskTests.swift │ │ ├── DeclarationFilterTaskTests.swift │ │ ├── DeclarationProducerTaskTests.swift │ │ ├── ExpressionCallUsageFilterTaskTests.swift │ │ ├── ExpressionCallValidationTaskTests.swift │ │ ├── Fixtures │ │ ├── ConcreteSubclass.swift │ │ ├── GenericSuperConcreteSubclass.swift │ │ ├── HasAbstractMethodClass.swift │ │ ├── HasAbstractVarClass.swift │ │ ├── InnerConcreteSubclass.swift │ │ ├── Integration │ │ │ ├── Invalid │ │ │ │ ├── ExpressionCall │ │ │ │ │ ├── AbstractInstantiation.swift │ │ │ │ │ ├── GrandParentAbstract.swift │ │ │ │ │ └── ParentAbstractChildAbstractNested.swift │ │ │ │ ├── GenericSuperclass │ │ │ │ │ ├── ChildConcrete.swift │ │ │ │ │ ├── GrandParentAbstract.swift │ │ │ │ │ └── ParentAbstractChildAbstractNested.swift │ │ │ │ └── Implementation │ │ │ │ │ ├── ChildConcrete.swift │ │ │ │ │ ├── GrandParentAbstract.swift │ │ │ │ │ └── ParentAbstractChildAbstractNested.swift │ │ │ └── Valid │ │ │ │ ├── ChildConcrete.swift │ │ │ │ ├── GrandParentAbstract.swift │ │ │ │ └── ParentAbstractChildAbstractNested.swift │ │ ├── MiddleAbstractClass.swift │ │ ├── MixedAbstractClasses.swift │ │ ├── NestedAbstractClasses.swift │ │ ├── NoAbstractClass.swift │ │ ├── UsageSubclass.swift │ │ ├── UsageType.swift │ │ └── ViolateInstantiation.swift │ │ ├── SubclassUsageFilterTaskTests.swift │ │ └── ValidatorTests.swift ├── bin │ └── abstractclassvalidator └── xcode.xcconfig └── foundation.xcconfig /.gitignore: -------------------------------------------------------------------------------- 1 | ## OSX 2 | *.DS_Store 3 | 4 | # Xcode Include to support Carthage for the Foundation project. 5 | # *.xcodeproj 6 | # *.xcworkspace 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | Packages/ 38 | Package.pins 39 | Package.resolved 40 | .build/ 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.1 3 | before_script: 4 | - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash" 5 | matrix: 6 | include: 7 | - name: "AbstractClassFoundationTests" 8 | script: xcodebuild test -project AbstractClassFoundation.xcodeproj -scheme AbstractClassFoundationTests -destination 'platform=iOS Simulator,OS=11.4,name=iPhone X' 9 | script: 10 | - fossa -------------------------------------------------------------------------------- /AbstractClassFoundation.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AbstractClassFoundation' 3 | s.version = '0.3.0' 4 | s.summary = 'Swift abstract class validator.' 5 | s.description = 'Swift abstract class validator is an source code analysis tool that validates abstract class rules.' 6 | 7 | s.homepage = 'https://github.com/uber/swift-abstract-class' 8 | s.license = { :type => 'Apache 2.0', :file => 'LICENSE.txt' } 9 | s.author = { 'Yi Wang' => 'yiw@uber.com' } 10 | 11 | s.source = { :git => 'https://github.com/uber/swift-abstract-class.git', :tag => "v" + s.version.to_s } 12 | s.source_files = 'Sources/**/*.swift' 13 | s.ios.deployment_target = '8.0' 14 | s.swift_version = '4.2' 15 | end 16 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/AbstractClassFoundationTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/AbstractClassFoundation_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = "1"; 4 | objectVersion = "46"; 5 | objects = { 6 | "AbstractClassFoundation::AbstractClassFoundation" = { 7 | isa = "PBXNativeTarget"; 8 | buildConfigurationList = "OBJ_19"; 9 | buildPhases = ( 10 | "OBJ_22", 11 | "OBJ_24" 12 | ); 13 | dependencies = ( 14 | ); 15 | name = "AbstractClassFoundation"; 16 | productName = "AbstractClassFoundation"; 17 | productReference = "AbstractClassFoundation::AbstractClassFoundation::Product"; 18 | productType = "com.apple.product-type.framework"; 19 | }; 20 | "AbstractClassFoundation::AbstractClassFoundation::Product" = { 21 | isa = "PBXFileReference"; 22 | path = "AbstractClassFoundation.framework"; 23 | sourceTree = "BUILT_PRODUCTS_DIR"; 24 | }; 25 | "AbstractClassFoundation::AbstractClassFoundationPackageTests::ProductTarget" = { 26 | isa = "PBXAggregateTarget"; 27 | buildConfigurationList = "OBJ_32"; 28 | buildPhases = ( 29 | ); 30 | dependencies = ( 31 | "OBJ_35" 32 | ); 33 | name = "AbstractClassFoundationPackageTests"; 34 | productName = "AbstractClassFoundationPackageTests"; 35 | }; 36 | "AbstractClassFoundation::AbstractClassFoundationTests" = { 37 | isa = "PBXNativeTarget"; 38 | buildConfigurationList = "OBJ_37"; 39 | buildPhases = ( 40 | "OBJ_40", 41 | "OBJ_42" 42 | ); 43 | dependencies = ( 44 | "OBJ_44" 45 | ); 46 | name = "AbstractClassFoundationTests"; 47 | productName = "AbstractClassFoundationTests"; 48 | productReference = "AbstractClassFoundation::AbstractClassFoundationTests::Product"; 49 | productType = "com.apple.product-type.bundle.unit-test"; 50 | }; 51 | "AbstractClassFoundation::AbstractClassFoundationTests::Product" = { 52 | isa = "PBXFileReference"; 53 | path = "AbstractClassFoundationTests.xctest"; 54 | sourceTree = "BUILT_PRODUCTS_DIR"; 55 | }; 56 | "AbstractClassFoundation::SwiftPMPackageDescription" = { 57 | isa = "PBXNativeTarget"; 58 | buildConfigurationList = "OBJ_26"; 59 | buildPhases = ( 60 | "OBJ_29" 61 | ); 62 | dependencies = ( 63 | ); 64 | name = "AbstractClassFoundationPackageDescription"; 65 | productName = "AbstractClassFoundationPackageDescription"; 66 | productType = "com.apple.product-type.framework"; 67 | }; 68 | "OBJ_1" = { 69 | isa = "PBXProject"; 70 | attributes = { 71 | LastUpgradeCheck = "9999"; 72 | }; 73 | buildConfigurationList = "OBJ_2"; 74 | compatibilityVersion = "Xcode 3.2"; 75 | developmentRegion = "English"; 76 | hasScannedForEncodings = "0"; 77 | knownRegions = ( 78 | "en" 79 | ); 80 | mainGroup = "OBJ_5"; 81 | productRefGroup = "OBJ_15"; 82 | projectDirPath = "."; 83 | targets = ( 84 | "AbstractClassFoundation::AbstractClassFoundation", 85 | "AbstractClassFoundation::SwiftPMPackageDescription", 86 | "AbstractClassFoundation::AbstractClassFoundationPackageTests::ProductTarget", 87 | "AbstractClassFoundation::AbstractClassFoundationTests" 88 | ); 89 | }; 90 | "OBJ_10" = { 91 | isa = "PBXGroup"; 92 | children = ( 93 | "OBJ_11" 94 | ); 95 | name = "AbstractClassFoundation"; 96 | path = "Sources/AbstractClassFoundation"; 97 | sourceTree = "SOURCE_ROOT"; 98 | }; 99 | "OBJ_11" = { 100 | isa = "PBXFileReference"; 101 | path = "AbstractDefinitions.swift"; 102 | sourceTree = ""; 103 | }; 104 | "OBJ_12" = { 105 | isa = "PBXGroup"; 106 | children = ( 107 | "OBJ_13" 108 | ); 109 | name = "Tests"; 110 | path = ""; 111 | sourceTree = "SOURCE_ROOT"; 112 | }; 113 | "OBJ_13" = { 114 | isa = "PBXGroup"; 115 | children = ( 116 | "OBJ_14" 117 | ); 118 | name = "AbstractClassFoundationTests"; 119 | path = "Tests/AbstractClassFoundationTests"; 120 | sourceTree = "SOURCE_ROOT"; 121 | }; 122 | "OBJ_14" = { 123 | isa = "PBXFileReference"; 124 | path = "AbstractDefinitionsTests.swift"; 125 | sourceTree = ""; 126 | }; 127 | "OBJ_15" = { 128 | isa = "PBXGroup"; 129 | children = ( 130 | "AbstractClassFoundation::AbstractClassFoundationTests::Product", 131 | "AbstractClassFoundation::AbstractClassFoundation::Product" 132 | ); 133 | name = "Products"; 134 | path = ""; 135 | sourceTree = "BUILT_PRODUCTS_DIR"; 136 | }; 137 | "OBJ_19" = { 138 | isa = "XCConfigurationList"; 139 | buildConfigurations = ( 140 | "OBJ_20", 141 | "OBJ_21" 142 | ); 143 | defaultConfigurationIsVisible = "0"; 144 | defaultConfigurationName = "Release"; 145 | }; 146 | "OBJ_2" = { 147 | isa = "XCConfigurationList"; 148 | buildConfigurations = ( 149 | "OBJ_3", 150 | "OBJ_4" 151 | ); 152 | defaultConfigurationIsVisible = "0"; 153 | defaultConfigurationName = "Release"; 154 | }; 155 | "OBJ_20" = { 156 | isa = "XCBuildConfiguration"; 157 | baseConfigurationReference = "OBJ_8"; 158 | buildSettings = { 159 | ENABLE_TESTABILITY = "YES"; 160 | FRAMEWORK_SEARCH_PATHS = ( 161 | "$(inherited)", 162 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 163 | ); 164 | HEADER_SEARCH_PATHS = ( 165 | "$(inherited)" 166 | ); 167 | INFOPLIST_FILE = "AbstractClassFoundation.xcodeproj/AbstractClassFoundation_Info.plist"; 168 | LD_RUNPATH_SEARCH_PATHS = ( 169 | "$(inherited)", 170 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 171 | ); 172 | OTHER_CFLAGS = ( 173 | "$(inherited)" 174 | ); 175 | OTHER_LDFLAGS = ( 176 | "$(inherited)" 177 | ); 178 | OTHER_SWIFT_FLAGS = ( 179 | "$(inherited)" 180 | ); 181 | PRODUCT_BUNDLE_IDENTIFIER = "AbstractClassFoundation"; 182 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 183 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 184 | SKIP_INSTALL = "YES"; 185 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 186 | "$(inherited)" 187 | ); 188 | SWIFT_VERSION = "4.0"; 189 | TARGET_NAME = "AbstractClassFoundation"; 190 | }; 191 | name = "Debug"; 192 | }; 193 | "OBJ_21" = { 194 | isa = "XCBuildConfiguration"; 195 | baseConfigurationReference = "OBJ_8"; 196 | buildSettings = { 197 | ENABLE_TESTABILITY = "YES"; 198 | FRAMEWORK_SEARCH_PATHS = ( 199 | "$(inherited)", 200 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 201 | ); 202 | HEADER_SEARCH_PATHS = ( 203 | "$(inherited)" 204 | ); 205 | INFOPLIST_FILE = "AbstractClassFoundation.xcodeproj/AbstractClassFoundation_Info.plist"; 206 | LD_RUNPATH_SEARCH_PATHS = ( 207 | "$(inherited)", 208 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 209 | ); 210 | OTHER_CFLAGS = ( 211 | "$(inherited)" 212 | ); 213 | OTHER_LDFLAGS = ( 214 | "$(inherited)" 215 | ); 216 | OTHER_SWIFT_FLAGS = ( 217 | "$(inherited)" 218 | ); 219 | PRODUCT_BUNDLE_IDENTIFIER = "AbstractClassFoundation"; 220 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 221 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 222 | SKIP_INSTALL = "YES"; 223 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 224 | "$(inherited)" 225 | ); 226 | SWIFT_VERSION = "4.0"; 227 | TARGET_NAME = "AbstractClassFoundation"; 228 | }; 229 | name = "Release"; 230 | }; 231 | "OBJ_22" = { 232 | isa = "PBXSourcesBuildPhase"; 233 | files = ( 234 | "OBJ_23" 235 | ); 236 | }; 237 | "OBJ_23" = { 238 | isa = "PBXBuildFile"; 239 | fileRef = "OBJ_11"; 240 | }; 241 | "OBJ_24" = { 242 | isa = "PBXFrameworksBuildPhase"; 243 | files = ( 244 | ); 245 | }; 246 | "OBJ_26" = { 247 | isa = "XCConfigurationList"; 248 | buildConfigurations = ( 249 | "OBJ_27", 250 | "OBJ_28" 251 | ); 252 | defaultConfigurationIsVisible = "0"; 253 | defaultConfigurationName = "Release"; 254 | }; 255 | "OBJ_27" = { 256 | isa = "XCBuildConfiguration"; 257 | buildSettings = { 258 | LD = "/usr/bin/true"; 259 | OTHER_SWIFT_FLAGS = ( 260 | "-swift-version", 261 | "4", 262 | "-I", 263 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4", 264 | "-target", 265 | "x86_64-apple-macosx10.10", 266 | "-sdk", 267 | "/Applications/Xcode.10.1.0.10B61.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk" 268 | ); 269 | SWIFT_VERSION = "4.0"; 270 | }; 271 | name = "Debug"; 272 | }; 273 | "OBJ_28" = { 274 | isa = "XCBuildConfiguration"; 275 | buildSettings = { 276 | LD = "/usr/bin/true"; 277 | OTHER_SWIFT_FLAGS = ( 278 | "-swift-version", 279 | "4", 280 | "-I", 281 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4", 282 | "-target", 283 | "x86_64-apple-macosx10.10", 284 | "-sdk", 285 | "/Applications/Xcode.10.1.0.10B61.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk" 286 | ); 287 | SWIFT_VERSION = "4.0"; 288 | }; 289 | name = "Release"; 290 | }; 291 | "OBJ_29" = { 292 | isa = "PBXSourcesBuildPhase"; 293 | files = ( 294 | "OBJ_30" 295 | ); 296 | }; 297 | "OBJ_3" = { 298 | isa = "XCBuildConfiguration"; 299 | buildSettings = { 300 | CLANG_ENABLE_OBJC_ARC = "YES"; 301 | COMBINE_HIDPI_IMAGES = "YES"; 302 | COPY_PHASE_STRIP = "NO"; 303 | DEBUG_INFORMATION_FORMAT = "dwarf"; 304 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 305 | ENABLE_NS_ASSERTIONS = "YES"; 306 | GCC_OPTIMIZATION_LEVEL = "0"; 307 | GCC_PREPROCESSOR_DEFINITIONS = ( 308 | "DEBUG=1", 309 | "$(inherited)" 310 | ); 311 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 312 | ONLY_ACTIVE_ARCH = "YES"; 313 | OTHER_SWIFT_FLAGS = ( 314 | "-DXcode" 315 | ); 316 | PRODUCT_NAME = "$(TARGET_NAME)"; 317 | SDKROOT = "macosx"; 318 | SUPPORTED_PLATFORMS = ( 319 | "macosx", 320 | "iphoneos", 321 | "iphonesimulator", 322 | "appletvos", 323 | "appletvsimulator", 324 | "watchos", 325 | "watchsimulator" 326 | ); 327 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 328 | "SWIFT_PACKAGE", 329 | "DEBUG" 330 | ); 331 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 332 | USE_HEADERMAP = "NO"; 333 | }; 334 | name = "Debug"; 335 | }; 336 | "OBJ_30" = { 337 | isa = "PBXBuildFile"; 338 | fileRef = "OBJ_6"; 339 | }; 340 | "OBJ_32" = { 341 | isa = "XCConfigurationList"; 342 | buildConfigurations = ( 343 | "OBJ_33", 344 | "OBJ_34" 345 | ); 346 | defaultConfigurationIsVisible = "0"; 347 | defaultConfigurationName = "Release"; 348 | }; 349 | "OBJ_33" = { 350 | isa = "XCBuildConfiguration"; 351 | buildSettings = { 352 | }; 353 | name = "Debug"; 354 | }; 355 | "OBJ_34" = { 356 | isa = "XCBuildConfiguration"; 357 | buildSettings = { 358 | }; 359 | name = "Release"; 360 | }; 361 | "OBJ_35" = { 362 | isa = "PBXTargetDependency"; 363 | target = "AbstractClassFoundation::AbstractClassFoundationTests"; 364 | }; 365 | "OBJ_37" = { 366 | isa = "XCConfigurationList"; 367 | buildConfigurations = ( 368 | "OBJ_38", 369 | "OBJ_39" 370 | ); 371 | defaultConfigurationIsVisible = "0"; 372 | defaultConfigurationName = "Release"; 373 | }; 374 | "OBJ_38" = { 375 | isa = "XCBuildConfiguration"; 376 | baseConfigurationReference = "OBJ_8"; 377 | buildSettings = { 378 | CLANG_ENABLE_MODULES = "YES"; 379 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 380 | FRAMEWORK_SEARCH_PATHS = ( 381 | "$(inherited)", 382 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 383 | ); 384 | HEADER_SEARCH_PATHS = ( 385 | "$(inherited)" 386 | ); 387 | INFOPLIST_FILE = "AbstractClassFoundation.xcodeproj/AbstractClassFoundationTests_Info.plist"; 388 | LD_RUNPATH_SEARCH_PATHS = ( 389 | "$(inherited)", 390 | "@loader_path/../Frameworks", 391 | "@loader_path/Frameworks" 392 | ); 393 | OTHER_CFLAGS = ( 394 | "$(inherited)" 395 | ); 396 | OTHER_LDFLAGS = ( 397 | "$(inherited)" 398 | ); 399 | OTHER_SWIFT_FLAGS = ( 400 | "$(inherited)" 401 | ); 402 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 403 | "$(inherited)" 404 | ); 405 | SWIFT_VERSION = "4.0"; 406 | TARGET_NAME = "AbstractClassFoundationTests"; 407 | }; 408 | name = "Debug"; 409 | }; 410 | "OBJ_39" = { 411 | isa = "XCBuildConfiguration"; 412 | baseConfigurationReference = "OBJ_8"; 413 | buildSettings = { 414 | CLANG_ENABLE_MODULES = "YES"; 415 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 416 | FRAMEWORK_SEARCH_PATHS = ( 417 | "$(inherited)", 418 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 419 | ); 420 | HEADER_SEARCH_PATHS = ( 421 | "$(inherited)" 422 | ); 423 | INFOPLIST_FILE = "AbstractClassFoundation.xcodeproj/AbstractClassFoundationTests_Info.plist"; 424 | LD_RUNPATH_SEARCH_PATHS = ( 425 | "$(inherited)", 426 | "@loader_path/../Frameworks", 427 | "@loader_path/Frameworks" 428 | ); 429 | OTHER_CFLAGS = ( 430 | "$(inherited)" 431 | ); 432 | OTHER_LDFLAGS = ( 433 | "$(inherited)" 434 | ); 435 | OTHER_SWIFT_FLAGS = ( 436 | "$(inherited)" 437 | ); 438 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 439 | "$(inherited)" 440 | ); 441 | SWIFT_VERSION = "4.0"; 442 | TARGET_NAME = "AbstractClassFoundationTests"; 443 | }; 444 | name = "Release"; 445 | }; 446 | "OBJ_4" = { 447 | isa = "XCBuildConfiguration"; 448 | buildSettings = { 449 | CLANG_ENABLE_OBJC_ARC = "YES"; 450 | COMBINE_HIDPI_IMAGES = "YES"; 451 | COPY_PHASE_STRIP = "YES"; 452 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 453 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 454 | GCC_OPTIMIZATION_LEVEL = "s"; 455 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 456 | OTHER_SWIFT_FLAGS = ( 457 | "-DXcode" 458 | ); 459 | PRODUCT_NAME = "$(TARGET_NAME)"; 460 | SDKROOT = "macosx"; 461 | SUPPORTED_PLATFORMS = ( 462 | "macosx", 463 | "iphoneos", 464 | "iphonesimulator", 465 | "appletvos", 466 | "appletvsimulator", 467 | "watchos", 468 | "watchsimulator" 469 | ); 470 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 471 | "SWIFT_PACKAGE" 472 | ); 473 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 474 | USE_HEADERMAP = "NO"; 475 | }; 476 | name = "Release"; 477 | }; 478 | "OBJ_40" = { 479 | isa = "PBXSourcesBuildPhase"; 480 | files = ( 481 | "OBJ_41" 482 | ); 483 | }; 484 | "OBJ_41" = { 485 | isa = "PBXBuildFile"; 486 | fileRef = "OBJ_14"; 487 | }; 488 | "OBJ_42" = { 489 | isa = "PBXFrameworksBuildPhase"; 490 | files = ( 491 | "OBJ_43" 492 | ); 493 | }; 494 | "OBJ_43" = { 495 | isa = "PBXBuildFile"; 496 | fileRef = "AbstractClassFoundation::AbstractClassFoundation::Product"; 497 | }; 498 | "OBJ_44" = { 499 | isa = "PBXTargetDependency"; 500 | target = "AbstractClassFoundation::AbstractClassFoundation"; 501 | }; 502 | "OBJ_5" = { 503 | isa = "PBXGroup"; 504 | children = ( 505 | "OBJ_6", 506 | "OBJ_7", 507 | "OBJ_9", 508 | "OBJ_12", 509 | "OBJ_15" 510 | ); 511 | path = ""; 512 | sourceTree = ""; 513 | }; 514 | "OBJ_6" = { 515 | isa = "PBXFileReference"; 516 | explicitFileType = "sourcecode.swift"; 517 | path = "Package.swift"; 518 | sourceTree = ""; 519 | }; 520 | "OBJ_7" = { 521 | isa = "PBXGroup"; 522 | children = ( 523 | "OBJ_8" 524 | ); 525 | name = "Configs"; 526 | path = ""; 527 | sourceTree = ""; 528 | }; 529 | "OBJ_8" = { 530 | isa = "PBXFileReference"; 531 | path = "foundation.xcconfig"; 532 | sourceTree = ""; 533 | }; 534 | "OBJ_9" = { 535 | isa = "PBXGroup"; 536 | children = ( 537 | "OBJ_10" 538 | ); 539 | name = "Sources"; 540 | path = ""; 541 | sourceTree = "SOURCE_ROOT"; 542 | }; 543 | }; 544 | rootObject = "OBJ_1"; 545 | } 546 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/xcshareddata/xcschemes/AbstractClassFoundation-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/xcshareddata/xcschemes/AbstractClassFoundation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /AbstractClassFoundation.xcodeproj/xcshareddata/xcschemes/AbstractClassFoundationTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mobile-open-source@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Swift Abstract Class 2 | 3 | Uber welcomes contributions of all kinds and sizes. This includes everything from from simple bug reports to large features. 4 | 5 | Before we can accept your contributions, we kindly ask you to sign our [Contributor License Agreement](https://cla-assistant.io/uber/swift-abstarct-class). 6 | 7 | Workflow 8 | -------- 9 | 10 | We love GitHub issues! 11 | 12 | For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice. 13 | 14 | For big features, please open an issue so that we can agree on the direction, and hopefully avoid investing a lot of time on a feature that might need reworking. 15 | 16 | Small pull requests for things like typos, bug fixes, etc are always welcome. 17 | 18 | DOs and DON'Ts 19 | -------------- 20 | 21 | * DO follow our [coding style](https://github.com/raywenderlich/swift-style-guide) 22 | * DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. 23 | * DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion. 24 | 25 | * DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. 26 | 27 | Guiding Principles 28 | ------------------ 29 | 30 | * We allow anyone to participate in our projects. Tasks can be carried out by anyone that demonstrates the capability to complete them 31 | * Always be respectful of one another. Assume the best in others and act with empathy at all times 32 | * Collaborate closely with individuals maintaining the project or experienced users. Getting ideas out in the open and seeing a proposal before it's a pull request helps reduce redundancy and ensures we're all connected to the decision making process 33 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/swift-abstract-class/0a4a83d9b0b91ad8228e50de39122e44775c722a/Cartfile -------------------------------------------------------------------------------- /FOUNDATION_README.md: -------------------------------------------------------------------------------- 1 | # Abstract Class Foundation Library 2 | 3 | ## Building and developing 4 | 5 | First resolve the dependencies: 6 | 7 | ``` 8 | $ swift package update 9 | ``` 10 | 11 | You can then build from the command-line: 12 | 13 | ``` 14 | $ swift build 15 | ``` 16 | 17 | Or create an Xcode project and build using the IDE: 18 | 19 | ``` 20 | $ swift package generate-xcodeproj --xcconfig-overrides foundation.xcconfig 21 | ``` 22 | Note: For now, the xcconfig is being used to pass in the iOS deployment target settings. 23 | 24 | **Once a Xcode project is generated using Swift Package Manager, the Xcode project's schemes must be recreated for both the `AbstractClassFoundation` framework as well as the `AbstractClassFoundationTests` test target.** This is required for Carthage and CI. 25 | 26 | ## Folder Structure 27 | 28 | In order for other projects to depend on `AbstractClassFoundation` via Swift Package Manager, the foundation project has to be at the reposiroty's root. At the same time, the folders cannot be named more specific to the foundation library, such as the `Sources` folder, due to SPM's strict requirements on folder structure. 29 | -------------------------------------------------------------------------------- /Images/build_phases.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/swift-abstract-class/0a4a83d9b0b91ad8228e50de39122e44775c722a/Images/build_phases.jpg -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY_FOLDER_PREFIX?=/usr/local 2 | BINARY_FOLDER=$(BINARY_FOLDER_PREFIX)/bin/ 3 | VALIDATOR_FOLDER=Validator 4 | VALIDATOR_ARCHIVE_PATH=$(shell cd $(VALIDATOR_FOLDER) && swift build $(SWIFT_BUILD_FLAGS) --show-bin-path)/abstractclassvalidator 5 | VALIDATOR_VERSION_FOLDER_PATH=$(VALIDATOR_FOLDER)/Sources/abstractclassvalidator 6 | VALIDATOR_VERSION_FILE_PATH=$(VALIDATOR_VERSION_FOLDER_PATH)/Version.swift 7 | SWIFT_BUILD_FLAGS=--disable-sandbox -c release -Xswiftc -static-stdlib 8 | 9 | .PHONY: clean build install uninstall 10 | 11 | clean: 12 | cd $(VALIDATOR_FOLDER) && swift package clean 13 | 14 | build: 15 | cd $(VALIDATOR_FOLDER) && swift build $(SWIFT_BUILD_FLAGS) 16 | 17 | install: uninstall clean build 18 | install -d "$(BINARY_FOLDER)" 19 | install "$(VALIDATOR_ARCHIVE_PATH)" "$(BINARY_FOLDER)" 20 | 21 | uninstall: 22 | rm -f "$(BINARY_FOLDER)/abstractclassvalidator" 23 | rm -f "/usr/local/bin/abstractclassvalidator" 24 | 25 | publish: 26 | git checkout master 27 | $(eval NEW_VERSION := $(filter-out $@, $(MAKECMDGOALS))) 28 | @sed 's/__VERSION_NUMBER__/$(NEW_VERSION)/g' $(VALIDATOR_VERSION_FOLDER_PATH)/Version.swift.template > $(VALIDATOR_VERSION_FILE_PATH) 29 | %: 30 | @: 31 | sed -i '' "s/\(s.version.*=.*'\).*\('\)/\1$(NEW_VERSION)\2/" AbstractClassFoundation.podspec 32 | make archive_validator 33 | git add $(VALIDATOR_FOLDER)/bin/abstractclassvalidator 34 | git add $(VALIDATOR_VERSION_FILE_PATH) 35 | git add AbstractClassFoundation.podspec 36 | $(eval NEW_VERSION_TAG := v$(NEW_VERSION)) 37 | git commit -m "Update validator binary and version file for $(NEW_VERSION_TAG)" 38 | git push origin master 39 | git tag $(NEW_VERSION_TAG) 40 | git push origin $(NEW_VERSION_TAG) 41 | $(eval NEW_VERSION_SHA := $(shell git rev-parse $(NEW_VERSION_TAG))) 42 | # Disabled until the project gets more traction so we can release it on brew. 43 | # brew update && brew bump-formula-pr --tag=$(NEW_VERSION_TAG) --revision=$(NEW_VERSION_SHA) abstractclassvalidator 44 | pod trunk push 45 | 46 | archive_validator: clean build 47 | mv $(VALIDATOR_ARCHIVE_PATH) $(VALIDATOR_FOLDER)/bin/ 48 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Swift Abstract Class depends on the following libraries: 2 | 3 | SourceKitten (https://github.com/jpsim/SourceKitten) 4 | Swift Package Manager (https://github.com/apple/swift-package-manager) 5 | Swift Concurrency (https://github.com/uber/swift-concurrency) 6 | Swift Common (https://github.com/uber/swift-common) 7 | 8 | Copyright (C) 2017 The Guava Authors 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); 11 | you may not use this file except in compliance with the License. 12 | You may obtain a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, 18 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | See the License for the specific language governing permissions and 20 | limitations under the License. 21 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "AbstractClassFoundation", 6 | products: [ 7 | .library(name: "AbstractClassFoundation", targets: ["AbstractClassFoundation"]) 8 | ], 9 | dependencies: [], 10 | targets: [ 11 | .target( 12 | name: "AbstractClassFoundation", 13 | dependencies: []), 14 | .testTarget( 15 | name: "AbstractClassFoundationTests", 16 | dependencies: ["AbstractClassFoundation"], 17 | exclude: []), 18 | ], 19 | swiftLanguageVersions: [4] 20 | ) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Abstract Class 2 | 3 | [![Build Status](https://travis-ci.com/uber/swift-abstract-class.svg?branch=master)](https://travis-ci.com/uber/swift-abstract-class?branch=master) 4 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 6 | 7 | Swift Abstract Class is a light-weight library along with an executable that enables compile-time safe [abstract class](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html) development for Swift projects. 8 | 9 | ## The gist 10 | 11 | This project contains two separate parts that work together to perform abstract class validation. The `AbstractClassFoundation` module contains a `protocol` that abstract classes should conform to. 12 | 13 | ```swift 14 | import AbstractClassFoundation 15 | 16 | class MyAbstractClass: AbstractClass { 17 | var anAbstractProperty: PropertyType { 18 | abstractMethod() 19 | } 20 | 21 | func anAbstractMethod(_ arg1: Arg, arg2: Arg) -> MyReturnType { 22 | abstractMethod() 23 | } 24 | 25 | func aConcreteMethod() { 26 | // Method implementation omitted. 27 | } 28 | } 29 | ``` 30 | 31 | Subclasses of an abstract class can also be abstract. 32 | 33 | ```swift 34 | class AnotherAbstractClass: MyAbstractClass { 35 | func anotherAbstractMethod() { 36 | abstractMethod() 37 | } 38 | } 39 | ``` 40 | 41 | The second part of this project is the `abstractclassvalidator` executable. The validator parses a specified set of Swift source files to generate an in-memory graph of class hierarchies. Based on the classes data models, it then validates the implementations against the abstract class rules. Rules include abstract classes cannot be directly instantaited; concrete subclasses of abstract classes must provide implementations of all abstract properties and methods in their class hierarchy. 42 | 43 | In order to enable this validation to be performed at compile-time, a Xcode pre-build run-script phase is added for the project. Please refer to [Validator Integration section](#Integrate-validator-with-Xcode) below for details. 44 | 45 | ## Installation 46 | 47 | ### Install `AbstractClassFoundation` framework 48 | 49 | #### Using [Carthage](https://github.com/Carthage/Carthage) 50 | 51 | Please follow the standard [Carthage installation process](https://github.com/Carthage/Carthage#quick-start) to integrate the `AbstractClassFoundation` framework with your Swift project. 52 | ``` 53 | github "https://github.com/uber/swift-abstract-class.git" ~> VERSION 54 | ``` 55 | 56 | #### Using [Swift Package Manager](https://github.com/apple/swift-package-manager) 57 | 58 | Please specify `AbstractClassFoundation` as a dependency via the standard [Swift Package Manager package definition process](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md) to integrate the `AbstractClassFoundation` framework with your Swift project. 59 | ``` 60 | dependencies: [ 61 | .package(url: "https://github.com/uber/swift-abstract-class.git", .upToNextMajor(from: "VERSION_NUMBER")), 62 | ], 63 | targets: [ 64 | .target( 65 | name: "YOUR_MODULE", 66 | dependencies: [ 67 | "AbstractClassFoundation", 68 | ]), 69 | ], 70 | ``` 71 | 72 | #### Using [CocoaPods](https://github.com/CocoaPods/CocoaPods) 73 | 74 | Please follow the standard pod integration process and use `AbstractClassFoundation` pod. 75 | 76 | ### Install validator 77 | 78 | #### Using [Carthage](https://github.com/Carthage/Carthage) 79 | 80 | If Carthage is used to integrate the `AbstractClassFoundation` framework, then a copy of the validator executable of the corresponding version is already downloaded in the Carthage folder. It can be found at `Carthage/Checkouts/swift-abstract-class/Validator/bin/abstractclassvalidator`. 81 | 82 | #### Using [Homebrew](https://github.com/Homebrew/brew) 83 | 84 | Regardless of how `AbstractClassFoundation` framework is integrated into your project, the validator can always be installed via [Homebrew](https://github.com/Homebrew/brew). 85 | ``` 86 | brew install abstractclassvalidator 87 | ``` 88 | 89 | ## Integrate validator with Xcode 90 | 91 | Even though the validator can be invoked from the commandline, it is most convenient when it's directly integrated with the build system. Since the vast marjority of Swift applications use Xcode as the build system, we'll cover this here. 92 | 93 | 1. Download the latest valiordat binary, either manually from the [Releases page](https://github.com/uber/swift-abstract-class/releases), or using [Carthage](https://github.com/Carthage/Carthage) or [Homebrew](https://github.com/Homebrew/brew). 94 | 2. Add a "Run Script" phase in the application's executable target's "Build Phases" section. ![](Images/build_phases.jpg) 95 | 3. Make sure the "Shell" value is `/bin/sh`. 96 | 4. Add a shell script that invokes the validator in the script box. For example, with binary downloaded using Carthage: `export SOURCEKIT_LOGGING=0 && ../Carthage/Checkouts/swift-abstract-class/Validator/bin/abstractclassvalidator validate Sources/`. 97 | * If installed via Carthage, the binary can be invoked by pointing to the Carthage checkout relative to where the Xcode project file is. In our sample, this path is `../Carthage/Checkouts/swift-abstract-class/Validator/bin/abstractclassvalidator validate`. 98 | * If installed via Homebrew, the binary can be executed by directly invoking `abstractclassvalidator validate` 99 | 100 | The first part of the script `export SOURCEKIT_LOGGING=0` silences the SourceKit logging, otherwise Xcode will display the logs as error messages. This is simply to reduce noise in Xcode. It isn't strictly required. The rest of the script invokes the validator executable, with a few arguments. If the validator is installed via Carthage, please keep in mind that the path to the validator executable binary is relative to the Xcode project's location. In our sample apps, the path is `../Carthage/Checkouts/swift-abstract-class/Validator/bin/abstractclassvalidator`. This might be different depending on your project's folder structure. The first argument `validate` tells the executable to run the code validation command. The second argument `Sources/` tells the validator where all the application source code is for parsing. Please refer to the section below for all possible parameters. 101 | 102 | That's it for the Xcode integration. Now every time Xcode builds the application, abstract class validator is run to ensure abstract classes and their concrete subclasses conform to abstract class rules. 103 | 104 | ## Validator parameters 105 | 106 | ### Available commands 107 | 108 | `validate`: Instructs the validator to parse Swift source files and validates abstract class rules are correctly followed. 109 | `version` prints the version of the validator. 110 | 111 | ### `validate` command 112 | 113 | #### Required positional parameters 114 | 1. Paths to the root folders of Swift source files, or text files containing paths of Swift source files with specified format. Any number of paths can be specified. All source list files must be of the same format. See below for more details on sources list file. For example, `Sources/ sources_list_file.txt`, instructs the validator to both recursively parse all Swift source files inside the "Sources" directory, as well as the source paths contained in the "sources_list_file.txt" file. 115 | 116 | #### Sources list file 117 | 118 | The validator can either parse all Swift files inside a directory including the ones in sub-directories, or if a file is specified, the validator assumes the file is a text file containing a list of Swift source file paths. The file is referred to as the sources list file. Two formats for this file are supported, `newline` and `minescaping`. With the `newline` format, the validator assumes each line in the sources list file is a single path to a Swift source file to be parsed. The `minescaping` format specifies that paths are escaped with single quotes if escaping is needed, while paths that don't require escaping are not wrapped with any quotes. All the paths are separated by a single space character. Use the `--sources-list-format` optional parameter to specify the format if necessary. 119 | 120 | If multiple sources list files are given to the `validate` command, they all have to have the same format. 121 | 122 | #### Optional parameters 123 | 124 | `--sources-list-format`: The format of the Swift sources list file. If this parameter is not specified, all sources list file is assumed to use the `newline` format. Please see [Sources list file](#Sources-list-file) section above for details. 125 | 126 | `--exclude-suffixes`: A list of file name suffixes to ignore for parsing. For example with `--exclude-suffixes Tests Mocks`, the validator will ignore any file whose name, excluding the file extension, ends with either "Test" or "Mocks". 127 | 128 | `--exclude-paths`: A list of path strings to ignore for parsing. For example with `--exclude-paths /sample /tests`, the validator will ignore any file whose path contains either "/sample" or "/tests". 129 | 130 | `--collect-parsing-info`: A boolean value indicating if information should be collected for parsing execution timeout errors. This defaults to `false`. 131 | 132 | `--timeout`: The timeout value, in seconds, to use for waiting on parsing and validating tasks. This defaults to 30 seconds. 133 | 134 | `--concurrency-limit`: The maximum number of tasks to execute concurrently. This defaults to the maximum concurrency allowed by the hardware. 135 | 136 | ## Related projects 137 | 138 | If you like Needle, check out other related open source projects from our team: 139 | - [Needle](https://github.com/uber/needle): a compile-time safe Swift dependency injection framework. 140 | - [Swift Concurrency](https://github.com/uber/swift-concurrency): a set of concurrency utility classes used by Uber, inspired by the equivalent [java.util.concurrent](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html) package classes. 141 | - [Swift Common](https://github.com/uber/swift-common): common libraries used by this set of Swift open source projects. 142 | 143 | ## License 144 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fuber%2Fswift-concurrency.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fuber%2Fswift-concurrency?ref=badge_large) 145 | -------------------------------------------------------------------------------- /Sources/AbstractClassFoundation/AbstractDefinitions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR COITIONS OF ANY KI, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A protocol declaring the conforming class as an abstract class. 20 | /// This means the conforming class cannot be directly instantiated, 21 | /// and it may contain abstract methods that subclasses must provide 22 | /// implementations for. 23 | public protocol AbstractClass: AnyObject { 24 | 25 | /// Using this definition declares the enclosing method as an abstract 26 | /// method, where subclasses of the enclosing class must override and 27 | /// provide a concrete implementation for. 28 | /// 29 | /// - note: Actual abstract classes that conform to the protocol 30 | /// `AbstractClass` should not actually implement this method. 31 | /// Instead a default implementation is provided via a protocol 32 | /// extension of the `AbstractClass` protocol. 33 | func abstractMethod(_ functionName: String) -> Never 34 | } 35 | 36 | /// Extension providing the default implementation of `abstractMethod`. 37 | public extension AbstractClass { 38 | 39 | /// Using this definition declares the enclosing method as an abstract 40 | /// method, where subclasses of the enclosing class must override and 41 | /// provide a concrete implementation for. 42 | public func abstractMethod(_ functionName: String = #function) -> Never { 43 | fatalError("Abstract method \(functionName) is not implemented.") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/AbstractClassFoundationTests/AbstractDefinitionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR COITIONS OF ANY KI, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassFoundation 19 | 20 | class AbstractDefinitionsTests: XCTestCase { 21 | 22 | func test_subclassDefinitions_verifyCompilationSuccess() { 23 | class MockAbstractClass: AbstractClass { 24 | 25 | var mockAbstractProperty: String { 26 | abstractMethod() 27 | } 28 | 29 | func mockAbstractMethod() -> Int { 30 | abstractMethod() 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Validator/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | *.xcodeproj 3 | *.xcworkspace 4 | -------------------------------------------------------------------------------- /Validator/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "AbstractClassValidator", 6 | products: [ 7 | .executable(name: "abstractclassvalidator", targets: ["abstractclassvalidator"]), 8 | .library(name: "AbstractClassValidatorFramework", targets: ["AbstractClassValidatorFramework"]) 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/jpsim/SourceKitten.git", from: "0.20.0"), 12 | .package(url: "https://github.com/apple/swift-package-manager.git", from: "0.1.0"), 13 | .package(url: "https://github.com/uber/swift-concurrency.git", .upToNextMajor(from: "0.7.1")), 14 | .package(url: "https://github.com/uber/swift-common.git", .branch("master")), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "abstractclassvalidator", 19 | dependencies: [ 20 | "CommandFramework", 21 | "AbstractClassValidatorFramework", 22 | ]), 23 | .target( 24 | name: "AbstractClassValidatorFramework", 25 | dependencies: [ 26 | "Utility", 27 | "SourceKittenFramework", 28 | "Concurrency", 29 | "SourceParsingFramework", 30 | ]), 31 | .testTarget( 32 | name: "AbstractClassValidatorFrameworkTests", 33 | dependencies: ["AbstractClassValidatorFramework"], 34 | exclude: [ 35 | "Fixtures", 36 | ]), 37 | ], 38 | swiftLanguageVersions: [4] 39 | ) 40 | -------------------------------------------------------------------------------- /Validator/README.md: -------------------------------------------------------------------------------- 1 | # Abstract Class Validator 2 | 3 | ## Building and developing 4 | 5 | ### Compiling from source: 6 | 7 | First resolve the dependencies: 8 | 9 | ``` 10 | $ swift package update 11 | ``` 12 | 13 | You can then build from the command-line: 14 | 15 | ``` 16 | $ swift build 17 | ``` 18 | 19 | Or create an Xcode project and build using the IDE: 20 | 21 | ``` 22 | $ swift package generate-xcodeproj --xcconfig-overrides xcode.xcconfig 23 | ``` 24 | Note: For now, the xcconfig is being used to pass in the DEBUG define. 25 | 26 | ### Debugging 27 | 28 | Abstract Class Validator is intended to be heavily multi-threaded. This makes stepping through the code rather complicated. To simplify the debugging process, set the `SINGLE_THREADED` enviroment variable for your `Run` configuration in the Scheme Editor to `1` or `YES`. 29 | 30 | ## Releasing 31 | 32 | ``` 33 | make archieve_validator 34 | ``` 35 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Models/AbstractClassDefinition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// The name of the base abstract method declaration. 20 | let abstractMethodType = "abstractMethod" 21 | 22 | /// A data model representing the definition of an abstract class. 23 | struct AbstractClassDefinition: Hashable { 24 | /// The name of the abstract class. 25 | let name: String 26 | /// The properties, both `var` and `let` of this class. 27 | let vars: [VarDefinition] 28 | /// The method definitions. 29 | let methods: [MethodDefinition] 30 | /// The names of inherited types. 31 | let inheritedTypes: [String] 32 | } 33 | 34 | /// A data model representing the definition of a method. 35 | struct MethodDefinition: Hashable { 36 | /// The name of the method. 37 | let name: String 38 | /// Indicates if this method is an abstract method. 39 | let isAbstract: Bool 40 | /// Indicates if this property has the override attribute. 41 | let isOverride: Bool 42 | // Parameter names do not need to be stored here, since the method 43 | // name encapsulates that already. The parameter label names do 44 | // no contribute to the uniqueness of methods. 45 | } 46 | 47 | /// A data model representing the definition of a computed 48 | /// property. 49 | struct VarDefinition: Hashable { 50 | /// The name of the property. 51 | let name: String 52 | /// Indicates if this property is an abstract method. 53 | let isAbstract: Bool 54 | /// Indicates if this property has the override attribute. 55 | let isOverride: Bool 56 | } 57 | 58 | /// A data model representing the definition of an abstract class that 59 | /// has been aggregated with its ancestor abstract classes. 60 | struct AggregatedAbstractClassDefinition: Hashable { 61 | /// The definition itself. 62 | let value: AbstractClassDefinition 63 | /// The aggregated properties, both `var` and `let` of this class 64 | /// and all the properties of this class's ancestors. 65 | let aggregatedVars: [VarDefinition] 66 | /// The aggregated method definitions, including all the method 67 | /// definitions of this class's ancestors. 68 | let aggregatedMethods: [MethodDefinition] 69 | } 70 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Models/ConcreteSubclassDefinition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A reference based model representing a concrete subclass of an abstract 20 | /// class. In other words, the leaf node in an abstract class hierarchy. 21 | struct ConcreteSubclassDefinition: Hashable { 22 | /// The name of the class. 23 | let name: String 24 | /// The properties, both `var` and `let` of this class. 25 | let vars: [VarDefinition] 26 | /// The methods of this class. 27 | let methods: [MethodDefinition] 28 | /// The names of inherited types. 29 | let inheritedTypes: [String] 30 | /// The file path where this definition is. 31 | let filePath: String 32 | } 33 | 34 | /// A data model representing the definition of a concrete subclass of an 35 | /// abstract class that has been aggregated with its ancestor abstract 36 | /// classes. 37 | struct AggregatedConcreteSubclassDefinition: Hashable { 38 | /// The definition itself. 39 | let value: ConcreteSubclassDefinition 40 | /// The aggregated properties, both `var` and `let` of this class 41 | /// and all the properties of this class's ancestors. 42 | let aggregatedVars: [VarDefinition] 43 | /// The aggregated method definitions, including all the method 44 | /// definitions of this class's ancestors. 45 | let aggregatedMethods: [MethodDefinition] 46 | } 47 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Models/ValidationResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// The result of validation tasks. 20 | enum ValidationResult { 21 | case success 22 | case failureWithReason(String) 23 | } 24 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Processors/AbstractClassDefinitionsAggregator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | /// A processing unit that aggregates abstract class definitions' 18 | /// abstract properties and methods with their ancestor abstract 19 | /// class definitions. 20 | class AbstractClassDefinitionsAggregator { 21 | 22 | /// Aggregate the given abstract class definitions based their 23 | /// class inheritance structures. 24 | /// 25 | /// - parameter abstractClassDefinitions: The definitions to 26 | /// aggregate. 27 | /// - returns: The aggregated abstract class definitions where 28 | /// sub-abstract class definitions include property and method 29 | /// definitions of their inherited super-abstract classes. 30 | func aggregate(abstractClassDefinitions: [AbstractClassDefinition]) -> [AggregatedAbstractClassDefinition] { 31 | // Create a map of name to definition for easy access. 32 | var definitionsMap = [String: ParentedAbstractClassDefinition]() 33 | for definition in abstractClassDefinitions { 34 | definitionsMap[definition.name] = ParentedAbstractClassDefinition(value: definition) 35 | } 36 | 37 | // Link individual definitions to their ancestor definitions. 38 | for (_, definition) in definitionsMap { 39 | let ancestors = definition.value.inheritedTypes.compactMap { (parentName: String) -> ParentedAbstractClassDefinition? in 40 | definitionsMap[parentName] 41 | } 42 | definition.ancestors.append(contentsOf: ancestors) 43 | } 44 | 45 | // Consolidate each definition with its ancestor definitions. 46 | return Array(definitionsMap.values).map { (definition: ParentedAbstractClassDefinition) -> AggregatedAbstractClassDefinition in 47 | var allVars = Set(definition.value.vars) 48 | var allMethods = Set(definition.value.methods) 49 | iterateAllAncestors(of: definition) { (ancestor: ParentedAbstractClassDefinition) in 50 | for varDefinition in ancestor.value.vars { 51 | allVars.insert(varDefinition) 52 | } 53 | for methodDefinition in ancestor.value.methods { 54 | allMethods.insert(methodDefinition) 55 | } 56 | } 57 | 58 | return AggregatedAbstractClassDefinition(value: definition.value, aggregatedVars: Array(allVars), aggregatedMethods: Array(allMethods)) 59 | } 60 | } 61 | 62 | // MARK: - Private 63 | 64 | private func iterateAllAncestors(of definition: ParentedAbstractClassDefinition, handler: (ParentedAbstractClassDefinition) -> ()) { 65 | var ancestors = definition.ancestors 66 | while !ancestors.isEmpty { 67 | let ancestor = ancestors.removeFirst() 68 | handler(ancestor) 69 | ancestors.append(contentsOf: ancestor.ancestors) 70 | } 71 | } 72 | } 73 | 74 | private class ParentedAbstractClassDefinition { 75 | fileprivate let value: AbstractClassDefinition 76 | fileprivate var ancestors = [ParentedAbstractClassDefinition]() 77 | 78 | fileprivate init(value: AbstractClassDefinition) { 79 | self.value = value 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Processors/ConcreteSubclassDefinitionsAggregator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | /// A processing unit that aggregates concrete subclass definitions' 18 | /// abstract properties and methods with their ancestor concrete subclass 19 | /// definitions. 20 | class ConcreteSubclassDefinitionsAggregator { 21 | 22 | /// Aggregate the given concrete subclass with their ancestor 23 | /// abstract class definitions based the inheritance structures. 24 | /// 25 | /// - SeeAlso: `AbstractClassDefinitionsAggregator`. 26 | /// - parameter leafConcreteSubclassDefinitions: The concrete 27 | /// subclass definitions at the leaf-level of class inheritance 28 | /// chains to aggregate. 29 | /// - parameter aggregatedAncestorAbstractClassDefinitions: The 30 | /// definitions of ancestor classes of the concrete subclasses. 31 | /// These definitions should already have their properties and 32 | /// methods aggregated. 33 | /// - returns: The aggregated concrete subclass definitions where 34 | /// definitions include concrete property and method definitions 35 | /// of their inherited superclasses. 36 | func aggregate(leafConcreteSubclassDefinitions: [ConcreteSubclassDefinition], aggregatedAncestorAbstractClassDefinitions: [AggregatedAbstractClassDefinition]) -> [AggregatedConcreteSubclassDefinition] { 37 | // Create a map of name to definition for easy access. 38 | var abstractClassDefinitionsMap = [String: AggregatedAbstractClassDefinition]() 39 | for definition in aggregatedAncestorAbstractClassDefinitions { 40 | abstractClassDefinitionsMap[definition.value.name] = definition 41 | } 42 | 43 | let parentedConcreteDefinitions = leafConcreteSubclassDefinitions.map { ParentedConcreteSubclassDefinition(value: $0) } 44 | 45 | // Link individual definitions to their ancestor definitions. 46 | for definition in parentedConcreteDefinitions { 47 | let ancestors = definition.value.inheritedTypes.compactMap { (parentName: String) -> AggregatedAbstractClassDefinition? in 48 | abstractClassDefinitionsMap[parentName] 49 | } 50 | definition.ancestors.append(contentsOf: ancestors) 51 | } 52 | 53 | // Consolidate each definition with its ancestor definitions. 54 | return parentedConcreteDefinitions.map { (definition: ParentedConcreteSubclassDefinition) -> AggregatedConcreteSubclassDefinition in 55 | let allAncestorVars = definition.ancestors.flatMap { $0.aggregatedVars } 56 | let allVars = Set(definition.value.vars).union(allAncestorVars) 57 | 58 | let allAncestorMethods = definition.ancestors.flatMap { $0.aggregatedMethods } 59 | let allMethods = Set(definition.value.methods).union(allAncestorMethods) 60 | 61 | return AggregatedConcreteSubclassDefinition(value: definition.value, aggregatedVars: Array(allVars), aggregatedMethods: Array(allMethods)) 62 | } 63 | } 64 | } 65 | 66 | private class ParentedConcreteSubclassDefinition { 67 | fileprivate let value: ConcreteSubclassDefinition 68 | fileprivate var ancestors = [AggregatedAbstractClassDefinition]() 69 | 70 | fileprivate init(value: ConcreteSubclassDefinition) { 71 | self.value = value 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/BaseRegexUsageFilterTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// A task that checks the various aspects of a file, including if its 22 | /// content contains any usages of known abstract classes that match a 23 | /// given regular expression, to determine if the file should to be 24 | /// processed further. 25 | class BaseRegexUsageFilterTask: BaseFileFilterTask { 26 | 27 | /// Initializer. 28 | /// 29 | /// - parameter url: The file URL to read from. 30 | /// - parameter exclusionSuffixes: The list of file name suffixes to 31 | /// check from. If the given URL filename's suffix matches any in the 32 | /// this list, the URL will be excluded. 33 | /// - parameter exclusionPaths: The list of path components to check. 34 | /// If the given URL's path contains any elements in this list, the 35 | /// URL will be excluded. 36 | /// - parameter abstractClassDefinitions: The definitions of all 37 | /// abstract classes. 38 | /// - parameter regexExpressionBuilder: The closure that creates a 39 | /// regular expression based on the given abstract class definition. 40 | init(url: URL, exclusionSuffixes: [String], exclusionPaths: [String], abstractClassDefinitions: [AbstractClassDefinition], taskId: TaskIds, regexExpressionBuilder: @escaping (AbstractClassDefinition) -> String) { 41 | self.abstractClassDefinitions = abstractClassDefinitions 42 | self.regexExpressionBuilder = regexExpressionBuilder 43 | super.init(url: url, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, taskId: taskId.rawValue) 44 | } 45 | 46 | /// Create a set of filters for the given file content. 47 | /// 48 | /// - parameter content: The file content the returned filters should 49 | /// be applied on. 50 | /// - returns: A set of filters to use on the given content. 51 | final override func filters(for content: String) -> [FileFilter] { 52 | return [ 53 | RegexUsageFilter(content: content, abstractClassDefinitions: abstractClassDefinitions, regexExpressionBuilder: regexExpressionBuilder) 54 | ] 55 | } 56 | 57 | // MARK: - Private 58 | 59 | private let abstractClassDefinitions: [AbstractClassDefinition] 60 | private let regexExpressionBuilder: (AbstractClassDefinition) -> String 61 | } 62 | 63 | private class RegexUsageFilter: FileFilter { 64 | 65 | fileprivate init(content: String, abstractClassDefinitions: [AbstractClassDefinition], regexExpressionBuilder: @escaping (AbstractClassDefinition) -> String) { 66 | self.content = content 67 | self.abstractClassDefinitions = abstractClassDefinitions 68 | self.regexExpressionBuilder = regexExpressionBuilder 69 | } 70 | 71 | fileprivate final func filter() -> Bool { 72 | // If there are no abstract classes, then there is no usage. 73 | if abstractClassDefinitions.isEmpty { 74 | return false 75 | } 76 | 77 | let expression = abstractClassDefinitions 78 | .map { (abstractClassDefinition: AbstractClassDefinition) -> String in 79 | regexExpressionBuilder(abstractClassDefinition) 80 | } 81 | .joined(separator: "|") 82 | let regex = Regex(expression) 83 | return regex.firstMatch(in: content) != nil 84 | } 85 | 86 | // MARK: - Private 87 | 88 | private let content: String 89 | private let abstractClassDefinitions: [AbstractClassDefinition] 90 | private let regexExpressionBuilder: (AbstractClassDefinition) -> String 91 | } 92 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/ConcreteSubclassProducerTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceKittenFramework 20 | import SourceParsingFramework 21 | 22 | /// A task that parses a Swift source content and produces concrete subclass 23 | /// of abstract class data models. 24 | class ConcreteSubclassProducerTask: AbstractTask<[ConcreteSubclassDefinition]> { 25 | 26 | /// Initializer. 27 | /// 28 | /// - parameter sourceUrl: The source URL. 29 | /// - parameter sourceContent: The source content to be parsed into AST. 30 | /// - parameter abstractClassDefinitions: The definitions of all 31 | /// abstract classes. 32 | init(sourceUrl: URL, sourceContent: String, abstractClassDefinitions: [AbstractClassDefinition]) { 33 | self.sourceUrl = sourceUrl 34 | self.sourceContent = sourceContent 35 | self.abstractClassDefinitions = abstractClassDefinitions 36 | super.init(id: TaskIds.concreteSubclassProducerTask.rawValue) 37 | } 38 | 39 | /// Execute the task and return the concrete subclass data models. 40 | /// 41 | /// - returns: The concrete subclass data models. 42 | /// - throws: Any error occurred during execution. 43 | override func execute() throws -> [ConcreteSubclassDefinition] { 44 | guard !abstractClassDefinitions.isEmpty else { 45 | return [] 46 | } 47 | 48 | let file = File(contents: sourceContent) 49 | do { 50 | let structure = try Structure(file: file) 51 | 52 | let abstractClassNames = Set(abstractClassDefinitions.map { (definition: AbstractClassDefinition) -> String in 53 | definition.name 54 | }) 55 | 56 | return structure 57 | .filterSubstructure(by: SwiftDeclarationKind.class.rawValue, recursively: true) 58 | .compactMap { (classStructure: Structure) -> ConcreteSubclassDefinition? in 59 | // If the class inherits from an abstract class, make 60 | // sure it is a concrete class. 61 | let inheritedTypes = classStructure.inheritedTypeNames 62 | if inheritedTypes.isAnyElement(in: abstractClassNames) { 63 | let hasAbstractVars = classStructure.computedVars.contains { $0.isAbstract } 64 | guard !hasAbstractVars else { 65 | return nil 66 | } 67 | let hasAbstractMethods = classStructure.methods.contains { $0.isAbstract } 68 | guard !hasAbstractMethods else { 69 | return nil 70 | } 71 | return ConcreteSubclassDefinition(name: classStructure.name, vars: classStructure.varDefinitions, methods: classStructure.methodDefinitions, inheritedTypes: inheritedTypes, filePath: sourceUrl.path) 72 | } 73 | return nil 74 | } 75 | } catch { 76 | throw GenericError.withMessage("Failed to parse AST for source at \(sourceUrl)") 77 | } 78 | } 79 | 80 | // MARK: - Private 81 | 82 | private let sourceUrl: URL 83 | private let sourceContent: String 84 | private let abstractClassDefinitions: [AbstractClassDefinition] 85 | } 86 | 87 | private extension Structure { 88 | 89 | var varDefinitions: [VarDefinition] { 90 | return filterSubstructure(by: SwiftDeclarationKind.varInstance.rawValue, recursively: false) 91 | .map { (varStructure) -> VarDefinition in 92 | VarDefinition(name: varStructure.name, isAbstract: false, isOverride: varStructure.isOverride) 93 | } 94 | } 95 | 96 | var methodDefinitions: [MethodDefinition] { 97 | return filterSubstructure(by: SwiftDeclarationKind.functionMethodInstance.rawValue, recursively: false) 98 | .map { (methodStructure) -> MethodDefinition in 99 | MethodDefinition(name: methodStructure.name, isAbstract: false, isOverride: methodStructure.isOverride) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/ConcreteSubclassValidationTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceKittenFramework 20 | import SourceParsingFramework 21 | 22 | /// A task that validates concrete subclasses of abstract classes must 23 | /// provide implementations for all abstract properties and methods 24 | /// declared in its class hierarchy. 25 | class ConcreteSubclassValidationTask: AbstractTask { 26 | 27 | /// Initializer. 28 | /// 29 | /// - parameter aggregatedConcreteSubclassDefinition: The aggregated 30 | /// concrete subclass definition where each definition includes 31 | /// concrete property and method definitions of its inherited 32 | /// superclasses. 33 | init(aggregatedConcreteSubclassDefinition: AggregatedConcreteSubclassDefinition) { 34 | self.aggregatedConcreteSubclassDefinition = aggregatedConcreteSubclassDefinition 35 | super.init(id: TaskIds.concreteSubclassValidationTask.rawValue) 36 | } 37 | 38 | /// Execute the task and validate the given aggregated concrete 39 | /// subclass definitions for inheritance usages. 40 | /// 41 | /// - returns: The validation result. 42 | /// - throws: Any error occurred during execution. 43 | override func execute() throws -> ValidationResult { 44 | // Check the entire inheritance chain's abstract properties and 45 | // methods are fulfilled by the entire chain. If GrandParent has 46 | // two abstract properties, Parent fulfills one and declares the 47 | // second one abstract still, the child only needs to fulfill 48 | // the second abstract property. If Parent fulfilled both, then 49 | // child should have been filtered out by the usage filter, since 50 | // Parent is not an abstract class. 51 | let result = validateVars(of: aggregatedConcreteSubclassDefinition) 52 | switch result { 53 | case .success: 54 | break 55 | case .failureWithReason(_): 56 | return result 57 | } 58 | 59 | return validateMethods(of: aggregatedConcreteSubclassDefinition) 60 | } 61 | 62 | // MARK: - Private 63 | 64 | private let aggregatedConcreteSubclassDefinition: AggregatedConcreteSubclassDefinition 65 | 66 | private func validateVars(of concreteDefinition: AggregatedConcreteSubclassDefinition) -> ValidationResult { 67 | // Cannot validate based on return type of the properties, since 68 | // the return types may be generic types. Using name and override 69 | // attribute is sufficient. 70 | let abstractVarNames = Set(concreteDefinition.aggregatedVars.filter { $0.isAbstract }.map { $0.name }) 71 | let concreteOverrideVarNames = Set(concreteDefinition.aggregatedVars.filter { !$0.isAbstract && $0.isOverride }.map { $0.name }) 72 | 73 | let nonImplementedVarNames = abstractVarNames.subtracting(concreteOverrideVarNames) 74 | if !nonImplementedVarNames.isEmpty { 75 | let varNames = nonImplementedVarNames.joined(separator: ", ") 76 | return .failureWithReason("Class \(concreteDefinition.value.name) is missing abstract property implementations of \(varNames) in \(concreteDefinition.value.filePath)") 77 | } 78 | 79 | return .success 80 | } 81 | 82 | private func validateMethods(of concreteDefinition: AggregatedConcreteSubclassDefinition) -> ValidationResult { 83 | // Cannot validate based on return type or parameter types, since 84 | // the these types may be generic types. Using name and override 85 | // attribute is sufficient. 86 | let abstractMethodNames = Set(concreteDefinition.aggregatedMethods.filter { $0.isAbstract }.map { $0.name }) 87 | let concreteOverrideMethodNames = Set(concreteDefinition.aggregatedMethods.filter { !$0.isAbstract && $0.isOverride }.map { $0.name }) 88 | 89 | let nonImplementedMethodNames = abstractMethodNames.subtracting(concreteOverrideMethodNames) 90 | if !nonImplementedMethodNames.isEmpty { 91 | let methodNames = nonImplementedMethodNames.joined(separator: ", ") 92 | return .failureWithReason("Class \(concreteDefinition.value.name) is missing abstract method implementations of \(methodNames) in \(concreteDefinition.value.filePath)") 93 | } 94 | 95 | return .success 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/DeclarationFilterTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// A task that checks the various aspects of a file, including if its 22 | /// content contains any abstract class delcarations, to determine if 23 | /// the file should to be processed further. 24 | class DeclarationFilterTask: BaseFileFilterTask { 25 | 26 | /// Initializer. 27 | /// 28 | /// - parameter url: The file URL to read from. 29 | /// - parameter exclusionSuffixes: The list of file name suffixes to 30 | /// check from. If the given URL filename's suffix matches any in the 31 | /// this list, the URL will be excluded. 32 | /// - parameter exclusionPaths: The list of path components to check. 33 | /// If the given URL's path contains any elements in this list, the 34 | /// URL will be excluded. 35 | init(url: URL, exclusionSuffixes: [String], exclusionPaths: [String]) { 36 | super.init(url: url, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, taskId: TaskIds.declarationsFilterTask.rawValue) 37 | } 38 | 39 | /// Create a set of filters for the given file content. 40 | /// 41 | /// - parameter content: The file content the returned filters should 42 | /// be applied on. 43 | /// - returns: A set of filters to use on the given content. 44 | override func filters(for content: String) -> [FileFilter] { 45 | return [ 46 | DeclarationFilter(content: content), 47 | ] 48 | } 49 | } 50 | 51 | private class DeclarationFilter: FileFilter { 52 | 53 | fileprivate init(content: String) { 54 | self.content = content 55 | } 56 | 57 | fileprivate final func filter() -> Bool { 58 | return content.contains("\(abstractMethodType)()") 59 | } 60 | 61 | // MARK: - Private 62 | 63 | private let content: String 64 | } 65 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/DeclarationProducerTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceKittenFramework 20 | import SourceParsingFramework 21 | 22 | /// A task that parses a Swift source content and produces abstract class 23 | /// declaration data models. 24 | class DeclarationProducerTask: AbstractTask<[AbstractClassDefinition]> { 25 | 26 | /// Initializer. 27 | /// 28 | /// - parameter sourceUrl: The source URL. 29 | /// - parameter sourceContent: The source content to be parsed into AST. 30 | init(sourceUrl: URL, sourceContent: String) { 31 | self.sourceUrl = sourceUrl 32 | self.sourceContent = sourceContent 33 | super.init(id: TaskIds.declarationsProducerTask.rawValue) 34 | } 35 | 36 | /// Execute the task and return the abstract class data models. 37 | /// 38 | /// - returns: The abstract class data models. 39 | /// - throws: Any error occurred during execution. 40 | override func execute() throws -> [AbstractClassDefinition] { 41 | let file = File(contents: sourceContent) 42 | do { 43 | let structure = try Structure(file: file) 44 | return structure 45 | .filterSubstructure(by: SwiftDeclarationKind.class.rawValue, recursively: true) 46 | .compactMap { (declaration: Structure) -> AbstractClassDefinition? in 47 | let vars = declaration.computedVars 48 | let hasAbstractVars = vars.contains { $0.isAbstract } 49 | let methods = declaration.methods 50 | let hasAbstractMethods = methods.contains { $0.isAbstract } 51 | if hasAbstractVars || hasAbstractMethods { 52 | return AbstractClassDefinition(name: declaration.name, vars: vars, methods: methods, inheritedTypes: declaration.inheritedTypeNames) 53 | } 54 | return nil 55 | } 56 | } catch { 57 | throw GenericError.withMessage("Failed to parse AST for source at \(sourceUrl)") 58 | } 59 | } 60 | 61 | // MARK: - Private 62 | 63 | private let sourceUrl: URL 64 | private let sourceContent: String 65 | } 66 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/ExpressionCallUsageFilterTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// A task that checks the various aspects of a file, including if its 22 | /// content contains any direct abstract class constructor invocations, 23 | /// to determine if the file should to be processed further. 24 | class ExpressionCallUsageFilterTask: BaseRegexUsageFilterTask { 25 | 26 | /// Initializer. 27 | /// 28 | /// - parameter url: The file URL to read from. 29 | /// - parameter exclusionSuffixes: The list of file name suffixes to 30 | /// check from. If the given URL filename's suffix matches any in the 31 | /// this list, the URL will be excluded. 32 | /// - parameter exclusionPaths: The list of path components to check. 33 | /// If the given URL's path contains any elements in this list, the 34 | /// URL will be excluded. 35 | /// - parameter abstractClassDefinitions: The definitions of all 36 | /// abstract classes. 37 | init(url: URL, exclusionSuffixes: [String], exclusionPaths: [String], abstractClassDefinitions: [AbstractClassDefinition]) { 38 | super.init(url: url, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, abstractClassDefinitions: abstractClassDefinitions, taskId: .expressionCallUsageFilterTask) { (abstractClassDefinition: AbstractClassDefinition) in 39 | "(\(abstractClassDefinition.name) *(.init)? *(\\(|\\{))" 40 | // Cannot filter out files that also invokes `abstractMethod`, 41 | // since a file might contain an abstract class and a concrete 42 | // implementation of an abstract class. 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/ExpressionCallValidationTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceKittenFramework 20 | import SourceParsingFramework 21 | 22 | /// A task that validates a file containing expression call abstract 23 | /// class usages to ensure abstract class types are not directly 24 | /// instantiated. 25 | class ExpressionCallValidationTask: AbstractTask { 26 | 27 | /// Initializer. 28 | /// 29 | /// - parameter sourceUrl: The source URL. 30 | /// - parameter sourceContent: The source content to be parsed into AST. 31 | /// - parameter abstractClassDefinitions: The definitions of all 32 | /// abstract classes. 33 | init(sourceUrl: URL, sourceContent: String, abstractClassDefinitions: [AbstractClassDefinition]) { 34 | self.sourceUrl = sourceUrl 35 | self.sourceContent = sourceContent 36 | self.abstractClassDefinitions = abstractClassDefinitions 37 | super.init(id: TaskIds.expressionCallValidationTask.rawValue) 38 | } 39 | 40 | /// Execute the task and validate the given file's expression call 41 | /// usages. 42 | /// 43 | /// - returns: The validation result. 44 | /// - throws: Any error occurred during execution. 45 | override func execute() throws -> ValidationResult { 46 | guard !abstractClassDefinitions.isEmpty else { 47 | return .success 48 | } 49 | 50 | let abstractClassNames = Set(abstractClassDefinitions.map { (definition: AbstractClassDefinition) -> String in 51 | definition.name 52 | }) 53 | 54 | let file = File(contents: sourceContent) 55 | guard let structure = try? Structure(file: file) else { 56 | throw GenericError.withMessage("Failed to parse AST for source at \(sourceUrl)") 57 | } 58 | 59 | // Remove explicit `init` annotations. 60 | let expressionCallTypes = structure.uniqueExpressionCallNames.compactMap { (call: String) -> String? in 61 | if call.hasSuffix(".init") { 62 | return call.components(separatedBy: ".").first 63 | } else { 64 | return call 65 | } 66 | } 67 | 68 | for type in expressionCallTypes { 69 | if abstractClassNames.contains(type) { 70 | return .failureWithReason("Abstract class \(type) should not be directly instantiated in file \(sourceUrl.path)") 71 | } 72 | } 73 | 74 | return .success 75 | } 76 | 77 | // MARK: - Private 78 | 79 | private let sourceUrl: URL 80 | private let sourceContent: String 81 | private let abstractClassDefinitions: [AbstractClassDefinition] 82 | } 83 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/SubclassUsageFilterTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// A task that checks the various aspects of a file, including if its 22 | /// content contains any subclasses of abstract classes, to determine if 23 | /// the file should to be processed further. 24 | class SubclassUsageFilterTask: BaseRegexUsageFilterTask { 25 | 26 | /// Initializer. 27 | /// 28 | /// - parameter url: The file URL to read from. 29 | /// - parameter exclusionSuffixes: The list of file name suffixes to 30 | /// check from. If the given URL filename's suffix matches any in the 31 | /// this list, the URL will be excluded. 32 | /// - parameter exclusionPaths: The list of path components to check. 33 | /// If the given URL's path contains any elements in this list, the 34 | /// URL will be excluded. 35 | /// - parameter abstractClassDefinitions: The definitions of all 36 | /// abstract classes. 37 | init(url: URL, exclusionSuffixes: [String], exclusionPaths: [String], abstractClassDefinitions: [AbstractClassDefinition]) { 38 | super.init(url: url, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, abstractClassDefinitions: abstractClassDefinitions, taskId: .subclassUsageFilterTask) { (abstractClassDefinition: AbstractClassDefinition) in 39 | "(: *\(abstractClassDefinition.name) *(\\(|\\{|\\<|,))" 40 | // Cannot filter out files that also contain known abstract 41 | // classes, since a file might contain an abstract class and 42 | // a concrete implementation of an abstract class. 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Tasks/TaskIds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A list of IDs of task types used in the validator. 20 | enum TaskIds: Int { 21 | case declarationsFilterTask = 1 22 | case declarationsProducerTask = 2 23 | case expressionCallUsageFilterTask = 3 24 | case subclassUsageFilterTask = 4 25 | case concreteSubclassProducerTask = 5 26 | case expressionCallValidationTask = 6 27 | case concreteSubclassValidationTask = 7 28 | } 29 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Utilities/ASTUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SourceKittenFramework 19 | 20 | /// Extension of SourceKitten `Structure` to provide easy access to a set 21 | /// of common abstract class AST properties. 22 | extension Structure { 23 | 24 | /// All the computed properties of this structure. This does not include 25 | /// recursive structures. 26 | var computedVars: [VarDefinition] { 27 | var definitions = [VarDefinition]() 28 | 29 | var substructures = self.substructures 30 | while !substructures.isEmpty { 31 | let sub = substructures.removeFirst() 32 | 33 | // Swift compiler ensures overriding computed properties must 34 | // have explicit types, and cannot be stored properties. If 35 | // the substructure does not have a return type, then it's not 36 | // a computed property. Therefore it cannot be abstract or an 37 | // override of an abstract property. So we do not have to parse. 38 | if let subType = sub.type, subType == .varInstance { 39 | // If next substructure is an expression call to `abstractMethod`, 40 | // then the current substructure is an abstract var. 41 | let isAbstract: Bool 42 | if let nextSub = substructures.first, nextSub.isExpressionCall && nextSub.name == abstractMethodType { 43 | isAbstract = true 44 | // Remove the next substructure since it is the expression 45 | // call to `abstractMethod`. 46 | _ = substructures.removeFirst() 47 | } else { 48 | isAbstract = false 49 | } 50 | 51 | // Properties must have return types. 52 | definitions.append(VarDefinition(name: sub.name, isAbstract: isAbstract, isOverride: sub.isOverride)) 53 | } 54 | } 55 | 56 | return definitions 57 | } 58 | 59 | /// All the instance methods of this structure. This does not 60 | /// include recursive structures. 61 | var methods: [MethodDefinition] { 62 | return filterSubstructure(by: SwiftDeclarationKind.functionMethodInstance.rawValue, recursively: false) 63 | .map { (methodStructure: Structure) -> MethodDefinition in 64 | // If method structure contains an expression call sub-structure 65 | // with the name `abstractMethod`, then this method is an abstract 66 | // method. 67 | let isAbstract = methodStructure.substructures.contains { (substructure: Structure) -> Bool in 68 | return substructure.isExpressionCall && substructure.name == abstractMethodType 69 | } 70 | return MethodDefinition(name: methodStructure.name, isAbstract: isAbstract, isOverride: methodStructure.isOverride) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Validator/Sources/AbstractClassValidatorFramework/Validator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// The set of errors the validator can throw. 22 | public enum ValidatorError: Error { 23 | /// The error with a message. 24 | case withMessage(String) 25 | } 26 | 27 | /// The entry point to the abstract class validator. 28 | public class Validator { 29 | 30 | /// Initializer. 31 | public init() {} 32 | 33 | /// Parse Swift source files by recurively scanning the given directories 34 | /// or source files included in the given source list files, excluding 35 | /// files with specified suffixes. Then validate the parsed code against 36 | /// the abstract class rules. 37 | /// 38 | /// - parameter sourceRootUrls: The directories or text files that contain 39 | /// a set of Swift source files to parse. 40 | /// - parameter sourcesListFormatValue: The optional `String` value of the 41 | /// format used by the sources list file. Use `nil` if the given 42 | /// `sourceRootPaths` is not a file containing a list of Swift source paths. 43 | /// - parameter exclusionSuffixes: The list of file name suffixes to 44 | /// check from. If a filename's suffix matches any in the this list, 45 | /// the file will not be parsed. 46 | /// - parameter exclusionPaths: The list of path components to check. 47 | /// If a file's URL path contains any elements in this list, the file 48 | /// will not be parsed. 49 | /// - parameter shouldCollectParsingInfo: `true` if dependency graph 50 | /// parsing information should be collected as tasks are executed. `false` 51 | /// otherwise. By collecting execution information, if waiting on the 52 | /// completion of a task sequence in the dependency parsing phase times out, 53 | /// the reported error contains the relevant information when the timeout 54 | /// occurred. The tracking does incur a minor performance cost. This value 55 | /// defaults to `false`. 56 | /// - parameter timeout: The timeout value, in seconds, to use for waiting 57 | /// on tasks. 58 | /// - parameter concurrencyLimit: The maximum number of tasks to execute 59 | /// concurrently. `nil` if no limit is set. 60 | /// - throws: `GeneratorError`. 61 | public final func validate(from sourceRootPaths: [String], withSourcesListFormat sourcesListFormatValue: String? = nil, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], shouldCollectParsingInfo: Bool, timeout: TimeInterval, concurrencyLimit: Int?) throws { 62 | let sourceRootUrls = sourceRootPaths.map { (path: String) -> URL in 63 | URL(path: path) 64 | } 65 | 66 | let executor = createExecutor(withName: "AbstractClassValidator.validate", shouldTrackTaskId: shouldCollectParsingInfo, concurrencyLimit: concurrencyLimit) 67 | let abstractClassDefinitions = try parseAbstractClassDefinitions(from: sourceRootUrls, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, using: executor, waitUpTo: timeout) 68 | 69 | try validateExpressionCalls(from: sourceRootUrls, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, against: abstractClassDefinitions, using: executor, waitUpTo: timeout) 70 | 71 | let aggregatedConcreteSubclassDefinitions = try parseAggregatedConcreteSubclassDefinitions(from: sourceRootUrls, of: abstractClassDefinitions, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, using: executor, waitUpTo: timeout) 72 | try validateImplementations(of: aggregatedConcreteSubclassDefinitions, using: executor, waitUpTo: timeout) 73 | } 74 | 75 | // MARK: - Private 76 | 77 | // MARK: - Task Execution 78 | 79 | private func createExecutor(withName name: String, shouldTrackTaskId: Bool, concurrencyLimit: Int?) -> SequenceExecutor { 80 | #if DEBUG 81 | return ProcessInfo().environment["SINGLE_THREADED"] != nil ? ImmediateSerialSequenceExecutor() : ConcurrentSequenceExecutor(name: name, qos: .userInteractive, shouldTrackTaskId: shouldTrackTaskId, maxConcurrentTasks: concurrencyLimit) 82 | #else 83 | return ConcurrentSequenceExecutor(name: name, qos: .userInteractive, shouldTrackTaskId: shouldTrackTaskId, maxConcurrentTasks: concurrencyLimit) 84 | #endif 85 | } 86 | 87 | private func executeAndCollectTaskHandles(with rootUrls: [URL], sourcesListFormatValue: String?, execution: (URL) -> SequenceExecutionHandle) throws -> [(SequenceExecutionHandle, URL)] { 88 | var urlHandles = [(SequenceExecutionHandle, URL)]() 89 | 90 | // Enumerate all files and execute parsing sequences concurrently. 91 | let enumerator = FileEnumerator() 92 | for url in rootUrls { 93 | try enumerator.enumerate(from: url, withSourcesListFormat: sourcesListFormatValue) { (fileUrl: URL) in 94 | let taskHandle = execution(fileUrl) 95 | urlHandles.append((taskHandle, fileUrl)) 96 | } 97 | } 98 | 99 | return urlHandles 100 | } 101 | 102 | // MARK: - Abstract Class Definitions 103 | 104 | private func parseAbstractClassDefinitions(from sourceRootUrls: [URL], withSourcesListFormat sourcesListFormatValue: String?, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], using executor: SequenceExecutor, waitUpTo timeout: TimeInterval) throws -> [AbstractClassDefinition] { 105 | // Parse all URLs. 106 | let urlTaskHandles = try executeAndCollectTaskHandles(with: sourceRootUrls, sourcesListFormatValue: sourcesListFormatValue) { (fileUrl: URL) -> SequenceExecutionHandle<[AbstractClassDefinition]> in 107 | let filterTask = DeclarationFilterTask(url: fileUrl, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths) 108 | 109 | return executor.executeSequence(from: filterTask) { (currentTask: Task, currentResult: Any) -> SequenceExecution<[AbstractClassDefinition]> in 110 | if currentTask is DeclarationFilterTask, let filterResult = currentResult as? FilterResult { 111 | switch filterResult { 112 | case .shouldProcess(let url, let content): 113 | return .continueSequence(DeclarationProducerTask(sourceUrl: url, sourceContent: content)) 114 | case .skip: 115 | return .endOfSequence([AbstractClassDefinition]()) 116 | } 117 | } else if currentTask is DeclarationProducerTask, let definitions = currentResult as? [AbstractClassDefinition] { 118 | return .endOfSequence(definitions) 119 | } else { 120 | fatalError("Unhandled task \(currentTask) with result \(currentResult)") 121 | } 122 | } 123 | } 124 | 125 | // Wait for parsing results. 126 | var definitions = [AbstractClassDefinition]() 127 | for urlHandle in urlTaskHandles { 128 | do { 129 | let result = try urlHandle.0.await(withTimeout: timeout) 130 | definitions.append(contentsOf: result) 131 | } catch SequenceExecutionError.awaitTimeout(let taskId) { 132 | throw GenericError.withMessage("Processing \(urlHandle.1.path) timed out while executing task with Id \(taskId)") 133 | } catch { 134 | throw error 135 | } 136 | } 137 | 138 | return definitions 139 | } 140 | 141 | // MARK: - Expression Call Validation 142 | 143 | private func validateExpressionCalls(from sourceRootUrls: [URL], withSourcesListFormat sourcesListFormatValue: String?, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], against abstractClassDefinitions: [AbstractClassDefinition], using executor: SequenceExecutor, waitUpTo timeout: TimeInterval) throws { 144 | let urlTaskHandles = try executeAndCollectTaskHandles(with: sourceRootUrls, sourcesListFormatValue: sourcesListFormatValue) { (fileUrl: URL) -> SequenceExecutionHandle in 145 | let filterTask = ExpressionCallUsageFilterTask(url: fileUrl, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, abstractClassDefinitions: abstractClassDefinitions) 146 | 147 | return executor.executeSequence(from: filterTask) { (currentTask: Task, currentResult: Any) -> SequenceExecution in 148 | if currentTask is ExpressionCallUsageFilterTask, let filterResult = currentResult as? FilterResult { 149 | switch filterResult { 150 | case .shouldProcess(let url, let content): 151 | return .continueSequence(ExpressionCallValidationTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: abstractClassDefinitions)) 152 | case .skip: 153 | return .endOfSequence(.success) 154 | } 155 | } else if currentTask is ExpressionCallValidationTask, let validationResult = currentResult as? ValidationResult { 156 | return .endOfSequence(validationResult) 157 | } else { 158 | fatalError("Unhandled task \(currentTask) with result \(currentResult)") 159 | } 160 | } 161 | } 162 | 163 | for urlHandle in urlTaskHandles { 164 | do { 165 | let result = try urlHandle.0.await(withTimeout: timeout) 166 | switch result { 167 | case .success: 168 | break 169 | case .failureWithReason(let reason): 170 | throw GenericError.withMessage(reason) 171 | } 172 | } catch SequenceExecutionError.awaitTimeout(let taskId) { 173 | throw GenericError.withMessage("Processing \(urlHandle.1.path) timed out while executing task with Id \(taskId)") 174 | } catch { 175 | throw error 176 | } 177 | } 178 | } 179 | 180 | // MARK: - Subclass Definitions 181 | 182 | private func parseAggregatedConcreteSubclassDefinitions(from sourceRootUrls: [URL], of abstractClassDefinitions: [AbstractClassDefinition], withSourcesListFormat sourcesListFormatValue: String?, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], using executor: SequenceExecutor, waitUpTo timeout: TimeInterval) throws -> [AggregatedConcreteSubclassDefinition] { 183 | // Parse all URLs. 184 | let urlTaskHandles = try executeAndCollectTaskHandles(with: sourceRootUrls, sourcesListFormatValue: sourcesListFormatValue) { (fileUrl: URL) -> SequenceExecutionHandle<[ConcreteSubclassDefinition]> in 185 | let filterTask = SubclassUsageFilterTask(url: fileUrl, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, abstractClassDefinitions: abstractClassDefinitions) 186 | 187 | return executor.executeSequence(from: filterTask) { (currentTask: Task, currentResult: Any) -> SequenceExecution<[ConcreteSubclassDefinition]> in 188 | if currentTask is SubclassUsageFilterTask, let filterResult = currentResult as? FilterResult { 189 | switch filterResult { 190 | case .shouldProcess(let url, let content): 191 | return .continueSequence(ConcreteSubclassProducerTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: abstractClassDefinitions)) 192 | case .skip: 193 | return .endOfSequence([ConcreteSubclassDefinition]()) 194 | } 195 | } else if currentTask is ConcreteSubclassProducerTask, let definitions = currentResult as? [ConcreteSubclassDefinition] { 196 | return .endOfSequence(definitions) 197 | } else { 198 | fatalError("Unhandled task \(currentTask) with result \(currentResult)") 199 | } 200 | } 201 | } 202 | 203 | // Wait for parsing results. 204 | var definitions = [ConcreteSubclassDefinition]() 205 | for urlHandle in urlTaskHandles { 206 | do { 207 | let result = try urlHandle.0.await(withTimeout: timeout) 208 | definitions.append(contentsOf: result) 209 | } catch SequenceExecutionError.awaitTimeout(let taskId) { 210 | throw GenericError.withMessage("Processing \(urlHandle.1.path) timed out while executing task with Id \(taskId)") 211 | } catch { 212 | throw error 213 | } 214 | } 215 | 216 | let aggregatedAbstractClassDefinitions = AbstractClassDefinitionsAggregator().aggregate(abstractClassDefinitions: abstractClassDefinitions) 217 | return ConcreteSubclassDefinitionsAggregator().aggregate(leafConcreteSubclassDefinitions: definitions, aggregatedAncestorAbstractClassDefinitions: aggregatedAbstractClassDefinitions) 218 | } 219 | 220 | // MARK: - Concrete Subclass Validation 221 | 222 | private func validateImplementations(of aggregatedConcreteSubclassDefinitions: [AggregatedConcreteSubclassDefinition], using executor: SequenceExecutor, waitUpTo timeout: TimeInterval) throws { 223 | var taskHandles = [SequenceExecutionHandle]() 224 | for definition in aggregatedConcreteSubclassDefinitions { 225 | let task = ConcreteSubclassValidationTask(aggregatedConcreteSubclassDefinition: definition) 226 | let handle = executor.executeSequence(from: task) { (currentTask: Task, currentResult: Any) -> SequenceExecution in 227 | if currentTask is ConcreteSubclassValidationTask, let validationResult = currentResult as? ValidationResult { 228 | return .endOfSequence(validationResult) 229 | } else { 230 | fatalError("Unhandled task \(currentTask) with result \(currentResult)") 231 | } 232 | } 233 | taskHandles.append(handle) 234 | } 235 | 236 | for handle in taskHandles { 237 | let result = try handle.await(withTimeout: timeout) 238 | switch result { 239 | case .success: 240 | break 241 | case .failureWithReason(let reason): 242 | throw GenericError.withMessage(reason) 243 | } 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /Validator/Sources/abstractclassvalidator/ValidateCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import AbstractClassValidatorFramework 18 | import CommandFramework 19 | import Foundation 20 | import SourceParsingFramework 21 | import Utility 22 | 23 | /// The validate command provides the core functionality of Abstract 24 | /// Class Validator. It parses Swift source files specified by the 25 | /// given input, excluding files with specified suffixes. It then checks 26 | /// a set of abstract class rules to ensure they are not violated. 27 | class ValidateCommand: AbstractCommand { 28 | 29 | /// Initializer. 30 | /// 31 | /// - parameter parser: The argument parser to use. 32 | init(parser: ArgumentParser) { 33 | super.init(name: "validate", overview: "Validate abstract class rules for Swift source files in a directory or listed in a single text file.", parser: parser) 34 | } 35 | 36 | /// Setup the arguments using the given parser. 37 | /// 38 | /// - parameter parser: The argument parser to use. 39 | override func setupArguments(with parser: ArgumentParser) { 40 | super.setupArguments(with: parser) 41 | 42 | sourceRootPaths = parser.add(positional: "sourceRootPaths", kind: [String].self, strategy: ArrayParsingStrategy.upToNextOption, usage: "Paths to the root folders of Swift source files, or text files containing paths of Swift source files with specified format.", completion: .filename) 43 | sourcesListFormat = parser.add(option: "--sources-list-format", kind: String.self, usage: "The format of the Swift sources list file. See SourcesListFileFormat for supported format details", completion: .filename) 44 | excludeSuffixes = parser.add(option: "--exclude-suffixes", kind: [String].self, usage: "Filename suffix(es) without extensions to exclude from parsing.", completion: .filename) 45 | excludePaths = parser.add(option: "--exclude-paths", kind: [String].self, usage: "Paths components to exclude from parsing.") 46 | shouldCollectParsingInfo = parser.add(option: "--collect-parsing-info", shortName: "-cpi", kind: Bool.self, usage: "Whether or not to collect information for parsing execution timeout errors.") 47 | timeout = parser.add(option: "--timeout", kind: Int.self, usage: "The timeout value, in seconds, to use for waiting on parsing and validating tasks.") 48 | concurrencyLimit = parser.add(option: "--concurrency-limit", kind: Int.self, usage: "The maximum number of tasks to execute concurrently.") 49 | } 50 | 51 | /// Execute the command. 52 | /// 53 | /// - parameter arguments: The command line arguments to execute the 54 | /// command with. 55 | override func execute(with arguments: ArgumentParser.Result) { 56 | super.execute(with: arguments) 57 | 58 | if let sourceRootPaths = arguments.get(sourceRootPaths) { 59 | let sourcesListFormat = arguments.get(self.sourcesListFormat) ?? nil 60 | let excludeSuffixes = arguments.get(self.excludeSuffixes) ?? [] 61 | let excludePaths = arguments.get(self.excludePaths) ?? [] 62 | let shouldCollectParsingInfo = arguments.get(self.shouldCollectParsingInfo) ?? false 63 | let timeout = arguments.get(self.timeout, withDefault: defaultTimeout) 64 | let concurrencyLimit = arguments.get(self.concurrencyLimit) ?? nil 65 | 66 | do { 67 | try Validator().validate(from: sourceRootPaths, withSourcesListFormat: sourcesListFormat, excludingFilesEndingWith: excludeSuffixes, excludingFilesWithPaths: excludePaths, shouldCollectParsingInfo: shouldCollectParsingInfo, timeout: timeout, concurrencyLimit: concurrencyLimit) 68 | } catch GenericError.withMessage(let message) { 69 | print(message) 70 | exit(1) 71 | } catch { 72 | fatalError("Unknown error: \(error)") 73 | } 74 | } else { 75 | fatalError("Missing source files root directories.") 76 | } 77 | } 78 | 79 | // MARK: - Private 80 | 81 | private var sourceRootPaths: PositionalArgument<[String]>! 82 | private var sourcesListFormat: OptionArgument! 83 | private var excludeSuffixes: OptionArgument<[String]>! 84 | private var excludePaths: OptionArgument<[String]>! 85 | private var shouldCollectParsingInfo: OptionArgument! 86 | private var timeout: OptionArgument! 87 | private var concurrencyLimit: OptionArgument! 88 | } 89 | -------------------------------------------------------------------------------- /Validator/Sources/abstractclassvalidator/Version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | let version = "0.3.0" 18 | -------------------------------------------------------------------------------- /Validator/Sources/abstractclassvalidator/Version.swift.template: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | let version = "__VERSION_NUMBER__" 18 | -------------------------------------------------------------------------------- /Validator/Sources/abstractclassvalidator/VersionCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import CommandFramework 18 | import Foundation 19 | import Utility 20 | 21 | /// A command that returns the current version of the validator. 22 | class VersionCommand: AbstractCommand { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter parser: The argument parser to use. 27 | init(parser: ArgumentParser) { 28 | super.init(name: "version", overview: "The version of this validator.", parser: parser) 29 | } 30 | 31 | /// Execute the command. 32 | /// 33 | /// - parameter arguments: The command line arguments to execute the 34 | /// command with. 35 | override func execute(with arguments: ArgumentParser.Result) { 36 | print(version) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Validator/Sources/abstractclassvalidator/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Basic 18 | import CommandFramework 19 | import Foundation 20 | import Utility 21 | 22 | func main() { 23 | let parser = ArgumentParser(usage: " ", overview: "Abstract class validator.") 24 | let commands = initializeCommands(with: parser) 25 | let inputs = Array(CommandLine.arguments.dropFirst()) 26 | do { 27 | let args = try parser.parse(inputs) 28 | execute(commands, with: parser, args) 29 | } catch { 30 | fatalError("Command-line pasing error (use --help for help): \(error)") 31 | } 32 | } 33 | 34 | private func initializeCommands(with parser: ArgumentParser) -> [Command] { 35 | return [ 36 | VersionCommand(parser: parser), 37 | ValidateCommand(parser: parser) 38 | ] 39 | } 40 | 41 | private func execute(_ commands: [Command], with parser: ArgumentParser, _ args: ArgumentParser.Result) { 42 | if let subparserName = args.subparser(parser) { 43 | for command in commands { 44 | if subparserName == command.name { 45 | command.execute(with: args) 46 | } 47 | } 48 | } else { 49 | parser.printUsage(on: stdoutStream) 50 | } 51 | } 52 | 53 | main() 54 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/AbstractClassDefinitionsAggregatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassValidatorFramework 19 | 20 | class AbstractClassDefinitionsAggregatorTests: BaseFrameworkTests { 21 | 22 | func test_aggregate_withAncestors_verifyAggregatedResults() { 23 | let grandParentVars = [VarDefinition(name: "gV", isAbstract: true, isOverride: false)] 24 | let grandParentMethods = [MethodDefinition(name: "gM1", isAbstract: true, isOverride: false), MethodDefinition(name: "gM2", isAbstract: true, isOverride: false)] 25 | 26 | let parentVars = [VarDefinition(name: "pV1", isAbstract: true, isOverride: false), VarDefinition(name: "pV2", isAbstract: true, isOverride: false)] 27 | let parentMethods = [MethodDefinition(name: "gM", isAbstract: true, isOverride: false)] 28 | 29 | let childVars = [VarDefinition(name: "cV", isAbstract: true, isOverride: false)] 30 | let childMethods = [MethodDefinition(name: "cM", isAbstract: true, isOverride: false)] 31 | 32 | let definitions = [ 33 | AbstractClassDefinition(name: "GrandParent", vars: grandParentVars, methods: grandParentMethods, inheritedTypes: []), 34 | AbstractClassDefinition(name: "Parent", vars: parentVars, methods: parentMethods, inheritedTypes: ["GrandParent"]), 35 | AbstractClassDefinition(name: "Child", vars: childVars, methods: childMethods, inheritedTypes: ["Parent"]), 36 | ] 37 | 38 | let aggregator = AbstractClassDefinitionsAggregator() 39 | 40 | let results = aggregator.aggregate(abstractClassDefinitions: definitions) 41 | 42 | for definition in results { 43 | switch definition.value.name { 44 | case "Child": 45 | let allVars = grandParentVars + parentVars + childVars 46 | for v in allVars { 47 | XCTAssertTrue(definition.aggregatedVars.contains(v)) 48 | } 49 | let allMethods = grandParentMethods + parentMethods + childMethods 50 | for m in allMethods { 51 | XCTAssertTrue(definition.aggregatedMethods.contains(m)) 52 | } 53 | case "Parent": 54 | let allVars = grandParentVars + parentVars 55 | for v in allVars { 56 | XCTAssertTrue(definition.aggregatedVars.contains(v)) 57 | } 58 | let allMethods = grandParentMethods + parentMethods 59 | for m in allMethods { 60 | XCTAssertTrue(definition.aggregatedMethods.contains(m)) 61 | } 62 | case "GrandParent": 63 | for v in grandParentVars { 64 | XCTAssertTrue(definition.aggregatedVars.contains(v)) 65 | } 66 | for m in grandParentMethods { 67 | XCTAssertTrue(definition.aggregatedMethods.contains(m)) 68 | } 69 | default: 70 | XCTFail() 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/BaseFrameworkTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | 19 | class BaseFrameworkTests: XCTestCase { 20 | 21 | /// Retrieve the URL for a fixture file. 22 | /// 23 | /// - parameter file: The name of the file including extension. 24 | /// - returns: The fixture file URL. 25 | func fixtureUrl(for file: String) -> URL { 26 | return URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("Fixtures/\(file)") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/ConcreteSubclassDefinitionsAggregatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassValidatorFramework 19 | 20 | class ConcreteSubclassDefinitionsAggregatorTests: BaseFrameworkTests { 21 | 22 | func test_aggregate_withAncestors_verifyAggregatedResults() { 23 | let grandParentVars = [VarDefinition(name: "gV", isAbstract: true, isOverride: false)] 24 | let grandParentMethods = [MethodDefinition(name: "gM1", isAbstract: true, isOverride: false), MethodDefinition(name: "gM2", isAbstract: true, isOverride: false)] 25 | 26 | let parentVars = [VarDefinition(name: "pV1", isAbstract: true, isOverride: false), VarDefinition(name: "pV2", isAbstract: true, isOverride: false)] 27 | let parentMethods = [MethodDefinition(name: "gM", isAbstract: true, isOverride: false)] 28 | 29 | let childVars = [VarDefinition(name: "cV", isAbstract: true, isOverride: false)] 30 | let childMethods = [MethodDefinition(name: "cM", isAbstract: true, isOverride: false)] 31 | 32 | let grandParentAbstractClass = AbstractClassDefinition(name: "GrandParent", vars: grandParentVars, methods: grandParentMethods, inheritedTypes: []) 33 | let parentAbstractClass = AbstractClassDefinition(name: "Parent", vars: parentVars, methods: parentMethods, inheritedTypes: ["GrandParent"]) 34 | let childAbstractClass = AbstractClassDefinition(name: "Child", vars: childVars, methods: childMethods, inheritedTypes: ["Parent"]) 35 | let aggregatedAbstractClassDefinitions = [ 36 | AggregatedAbstractClassDefinition(value: grandParentAbstractClass, aggregatedVars: grandParentVars, aggregatedMethods: grandParentMethods), 37 | AggregatedAbstractClassDefinition(value: parentAbstractClass, aggregatedVars: grandParentVars + parentVars, aggregatedMethods: grandParentMethods + parentMethods), 38 | AggregatedAbstractClassDefinition(value: childAbstractClass, aggregatedVars: grandParentVars + parentVars + childVars, aggregatedMethods: grandParentMethods + parentMethods + childMethods), 39 | ] 40 | 41 | let parentLeafVars = [VarDefinition(name: "pLV", isAbstract: false, isOverride: true)] 42 | let parentLeafMethods = [MethodDefinition(name: "pLM", isAbstract: false, isOverride: true)] 43 | 44 | let childLeafVars = [VarDefinition(name: "cLV1", isAbstract: false, isOverride: true), 45 | VarDefinition(name: "cLV2", isAbstract: false, isOverride: true)] 46 | let childLeafMethods = [MethodDefinition(name: "cLM", isAbstract: false, isOverride: true)] 47 | 48 | let leafConcreteSubclassDefinitions = [ 49 | ConcreteSubclassDefinition(name: "ParentLeaf", vars: parentLeafVars, methods: parentLeafMethods, inheritedTypes: ["GrandParent", "Parent"], filePath: URL(fileURLWithPath: #file).path), 50 | ConcreteSubclassDefinition(name: "ChildLeaf", vars: childLeafVars, methods: childLeafMethods, inheritedTypes: ["GrandParent", "Parent", "Child"], filePath: URL(fileURLWithPath: #file).path) 51 | ] 52 | 53 | let aggregator = ConcreteSubclassDefinitionsAggregator() 54 | 55 | let results = aggregator.aggregate(leafConcreteSubclassDefinitions: leafConcreteSubclassDefinitions, aggregatedAncestorAbstractClassDefinitions: aggregatedAbstractClassDefinitions) 56 | 57 | for definition in results { 58 | switch definition.value.name { 59 | case "ChildLeaf": 60 | let allVars = grandParentVars + parentVars + childVars + childLeafVars 61 | for v in allVars { 62 | XCTAssertTrue(definition.aggregatedVars.contains(v)) 63 | } 64 | let allMethods = grandParentMethods + parentMethods + childMethods + childLeafMethods 65 | for m in allMethods { 66 | XCTAssertTrue(definition.aggregatedMethods.contains(m)) 67 | } 68 | case "ParentLeaf": 69 | let allVars = grandParentVars + parentVars + parentLeafVars 70 | for v in allVars { 71 | XCTAssertTrue(definition.aggregatedVars.contains(v)) 72 | } 73 | let allMethods = grandParentMethods + parentMethods + parentLeafMethods 74 | for m in allMethods { 75 | XCTAssertTrue(definition.aggregatedMethods.contains(m)) 76 | } 77 | default: 78 | XCTFail() 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/ConcreteSubclassProducerTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassValidatorFramework 19 | 20 | class ConcreteSubclassProducerTaskTests: BaseFrameworkTests { 21 | 22 | func test_execute_hasAbstractSubclass_verifyNoDefinitions() { 23 | let url = fixtureUrl(for: "MiddleAbstractClass.swift") 24 | let content = try! String(contentsOf: url) 25 | let abstractClassDefinitions = [AbstractClassDefinition(name: "GrandParentAbstractClass", vars: [VarDefinition(name: "grandParentVar", isAbstract: true, isOverride: false)], methods: [], inheritedTypes: ["AbstractClass"])] 26 | 27 | let task = ConcreteSubclassProducerTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: abstractClassDefinitions) 28 | 29 | let concreteDefinitions = try! task.execute() 30 | 31 | XCTAssertTrue(concreteDefinitions.isEmpty) 32 | } 33 | 34 | func test_execute_hasConcreteSubclasses_verifyDefinitions() { 35 | let url = fixtureUrl(for: "ConcreteSubclass.swift") 36 | let content = try! String(contentsOf: url) 37 | let abstractClassDefinitions = [ 38 | AbstractClassDefinition(name: "GrandParentAbstractClass", vars: [VarDefinition(name: "grandParentVar", isAbstract: true, isOverride: false)], methods: [], inheritedTypes: ["AbstractClass"]), 39 | AbstractClassDefinition(name: "ParentAbstractClass", vars: [], methods: [MethodDefinition(name: "parentMethod(index:)", isAbstract: true, isOverride: false)], inheritedTypes: ["GrandParentAbstractClass"]) 40 | ] 41 | 42 | let task = ConcreteSubclassProducerTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: abstractClassDefinitions) 43 | 44 | let concreteDefinitions = try! task.execute() 45 | 46 | XCTAssertEqual(concreteDefinitions.count, 2) 47 | 48 | XCTAssertEqual(concreteDefinitions[0].name, "ConcreteClass1") 49 | XCTAssertEqual(concreteDefinitions[0].vars, [VarDefinition(name: "someProperty", isAbstract: false, isOverride: false), 50 | VarDefinition(name: "grandParentVar", isAbstract: false, isOverride: true)]) 51 | XCTAssertEqual(concreteDefinitions[0].methods, [MethodDefinition(name: "parentMethod(index:)", isAbstract: false, isOverride: true)]) 52 | XCTAssertEqual(concreteDefinitions[0].inheritedTypes, ["ParentAbstractClass", "AProtocol"]) 53 | 54 | XCTAssertEqual(concreteDefinitions[1].name, "ConcreteClass2") 55 | XCTAssertEqual(concreteDefinitions[1].vars, [VarDefinition(name: "grandParentVar", isAbstract: false, isOverride: true)]) 56 | XCTAssertEqual(concreteDefinitions[1].inheritedTypes, ["GrandParentAbstractClass"]) 57 | } 58 | 59 | func test_execute_hasInnerConcreteSubclass_verifyDefinition() { 60 | let url = fixtureUrl(for: "InnerConcreteSubclass.swift") 61 | let content = try! String(contentsOf: url) 62 | let abstractClassDefinitions = [ 63 | AbstractClassDefinition(name: "GrandParentAbstractClass", vars: [VarDefinition(name: "grandParentVar", isAbstract: true, isOverride: false)], methods: [], inheritedTypes: ["AbstractClass"]), 64 | AbstractClassDefinition(name: "ParentAbstractClass", vars: [], methods: [MethodDefinition(name: "parentMethod(index:)", isAbstract: true, isOverride: false)], inheritedTypes: ["GrandParentAbstractClass"]) 65 | ] 66 | 67 | let task = ConcreteSubclassProducerTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: abstractClassDefinitions) 68 | 69 | let concreteDefinitions = try! task.execute() 70 | 71 | XCTAssertEqual(concreteDefinitions.count, 1) 72 | 73 | XCTAssertEqual(concreteDefinitions[0].name, "ConcreteClass2") 74 | XCTAssertEqual(concreteDefinitions[0].vars, [VarDefinition(name: "grandParentVar", isAbstract: false, isOverride: true)]) 75 | XCTAssertEqual(concreteDefinitions[0].inheritedTypes, ["GrandParentAbstractClass"]) 76 | } 77 | 78 | func test_execute_hasGenericBaseClassAndConcreteSubclass_verifyDefinition() { 79 | let url = fixtureUrl(for: "GenericSuperConcreteSubclass.swift") 80 | let content = try! String(contentsOf: url) 81 | let abstractClassDefinitions = [ 82 | AbstractClassDefinition(name: "GenericBaseClass", vars: [VarDefinition(name: "genericTypeVar", isAbstract: true, isOverride: false)], methods: [], inheritedTypes: ["AbstractClass"]), 83 | AbstractClassDefinition(name: "ParentAbstractClass", vars: [], methods: [MethodDefinition(name: "parentMethod(index:)", isAbstract: true, isOverride: false)], inheritedTypes: ["GrandParentAbstractClass"]) 84 | ] 85 | 86 | let task = ConcreteSubclassProducerTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: abstractClassDefinitions) 87 | 88 | let concreteDefinitions = try! task.execute() 89 | 90 | XCTAssertEqual(concreteDefinitions.count, 1) 91 | 92 | XCTAssertEqual(concreteDefinitions[0].name, "ConcreteClassGenericType") 93 | XCTAssertEqual(concreteDefinitions[0].vars.count, 1) 94 | XCTAssertEqual(concreteDefinitions[0].vars, [VarDefinition(name: "genericTypeVar", isAbstract: false, isOverride: true)]) 95 | XCTAssertEqual(concreteDefinitions[0].methods.count, 1) 96 | XCTAssertEqual(concreteDefinitions[0].methods[0].name, "returnGenericType()") 97 | XCTAssertEqual(concreteDefinitions[0].inheritedTypes, ["GenericBaseClass", "AProtocol"]) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/ConcreteSubclassValidationTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SourceParsingFramework 18 | import XCTest 19 | @testable import AbstractClassValidatorFramework 20 | 21 | class ConcreteSubclassValidationTaskTests: BaseFrameworkTests { 22 | 23 | private var grandParentAbstractVars: [VarDefinition]! 24 | private var grandParentConcreteVars: [VarDefinition]! 25 | 26 | private var grandParentAbstractMethods: [MethodDefinition]! 27 | private var grandParentConcreteMethods: [MethodDefinition]! 28 | 29 | private var parentAbstractVars: [VarDefinition]! 30 | private var parentConcreteVars: [VarDefinition]! 31 | 32 | private var parentAbstractMethods: [MethodDefinition]! 33 | private var parentConcreteMethods: [MethodDefinition]! 34 | 35 | private var childAbstractVars: [VarDefinition]! 36 | private var childConcreteVars: [VarDefinition]! 37 | 38 | private var childAbstractMethods: [MethodDefinition]! 39 | private var childConcreteMethods: [MethodDefinition]! 40 | 41 | private var parentAdditionalConcreteVars: [VarDefinition]! 42 | private var parentAdditionalConcreteMethods: [MethodDefinition]! 43 | 44 | private var childAdditionalConcreteVars: [VarDefinition]! 45 | private var childAdditionalConcreteMethods: [MethodDefinition]! 46 | 47 | private var parentConcreteClass: ConcreteSubclassDefinition! 48 | private var childConcreteClass: ConcreteSubclassDefinition! 49 | 50 | override func setUp() { 51 | super.setUp() 52 | 53 | grandParentAbstractVars = [VarDefinition(name: "gV", isAbstract: true, isOverride: false)] 54 | grandParentConcreteVars = [VarDefinition(name: "gV", isAbstract: false, isOverride: true)] 55 | 56 | grandParentAbstractMethods = [MethodDefinition(name: "gM1", isAbstract: true, isOverride: false), MethodDefinition(name: "gM2(_:arg2:)", isAbstract: true, isOverride: false)] 57 | grandParentConcreteMethods = [MethodDefinition(name: "gM1", isAbstract: false, isOverride: true), MethodDefinition(name: "gM2(_:arg2:)", isAbstract: false, isOverride: true)] 58 | 59 | parentAbstractVars = [VarDefinition(name: "pV1", isAbstract: true, isOverride: false), VarDefinition(name: "pV2", isAbstract: true, isOverride: false)] 60 | parentConcreteVars = [VarDefinition(name: "pV1", isAbstract: false, isOverride: true), VarDefinition(name: "pV2", isAbstract: false, isOverride: true)] 61 | 62 | parentAbstractMethods = [MethodDefinition(name: "gM", isAbstract: true, isOverride: false)] 63 | parentConcreteMethods = [MethodDefinition(name: "gM", isAbstract: false, isOverride: true)] 64 | 65 | childAbstractVars = [VarDefinition(name: "cV", isAbstract: true, isOverride: false)] 66 | childConcreteVars = [VarDefinition(name: "cV", isAbstract: false, isOverride: true)] 67 | 68 | childAbstractMethods = [MethodDefinition(name: "cM(arg:)", isAbstract: true, isOverride: false)] 69 | childConcreteMethods = [MethodDefinition(name: "cM(arg:)", isAbstract: false, isOverride: true)] 70 | 71 | parentAdditionalConcreteVars = [VarDefinition(name: "pLV", isAbstract: false, isOverride: true)] 72 | parentAdditionalConcreteMethods = [MethodDefinition(name: "pLM", isAbstract: false, isOverride: true)] 73 | 74 | childAdditionalConcreteVars = [VarDefinition(name: "cLV1", isAbstract: false, isOverride: true), 75 | VarDefinition(name: "cLV2", isAbstract: false, isOverride: true)] 76 | childAdditionalConcreteMethods = [MethodDefinition(name: "cLM", isAbstract: false, isOverride: true)] 77 | 78 | parentConcreteClass = ConcreteSubclassDefinition(name: "ParentLeaf", vars: parentAdditionalConcreteVars, methods: parentAdditionalConcreteMethods, inheritedTypes: ["GrandParent", "Parent"], filePath: URL(fileURLWithPath: #file).path) 79 | childConcreteClass = ConcreteSubclassDefinition(name: "ChildLeaf", vars: childAdditionalConcreteVars, methods: childAdditionalConcreteMethods, inheritedTypes: ["GrandParent", "Parent", "Child"], filePath: URL(fileURLWithPath: #file).path) 80 | } 81 | 82 | func test_execute_hasValidDefinitions_verifyNoError() { 83 | // Splitting these expressions since Swift compiler thinks it's too complex. 84 | let childVars1 = grandParentAbstractVars + grandParentConcreteVars 85 | let childVars2 = parentAbstractVars + parentConcreteVars + parentAdditionalConcreteVars 86 | let childVars3 = childAbstractVars + childConcreteVars + childAdditionalConcreteVars 87 | let childMethods1 = grandParentAbstractMethods + grandParentConcreteMethods 88 | let childMethods2 = parentAbstractMethods + parentConcreteMethods + parentAdditionalConcreteMethods 89 | let childMethods3 = childAbstractMethods + childConcreteMethods + childAdditionalConcreteMethods 90 | let aggregatedChildClass = AggregatedConcreteSubclassDefinition(value: childConcreteClass, aggregatedVars: childVars1 + childVars2 + childVars3, aggregatedMethods: childMethods1 + childMethods2 + childMethods3) 91 | 92 | let task = ConcreteSubclassValidationTask(aggregatedConcreteSubclassDefinition: aggregatedChildClass) 93 | 94 | let result = try! task.execute() 95 | switch result { 96 | case .success: 97 | break 98 | default: 99 | XCTFail() 100 | } 101 | } 102 | 103 | func test_execute_missingMiddleVarDefinitions_verifyError() { 104 | // Splitting these expressions since Swift compiler thinks it's too complex. 105 | let parentVars1 = grandParentAbstractVars + grandParentConcreteVars 106 | let parentVars2 = parentAbstractVars + parentAdditionalConcreteVars 107 | let parentMethods1 = grandParentAbstractMethods + grandParentConcreteMethods 108 | let parentMethods2 = parentAbstractMethods + parentConcreteMethods + parentAdditionalConcreteMethods 109 | let aggregatedParentClass = AggregatedConcreteSubclassDefinition(value: parentConcreteClass, aggregatedVars: parentVars1 + parentVars2, aggregatedMethods: parentMethods1 + parentMethods2) 110 | 111 | let task = ConcreteSubclassValidationTask(aggregatedConcreteSubclassDefinition: aggregatedParentClass) 112 | 113 | let result = try! task.execute() 114 | switch result { 115 | case .failureWithReason(let reason): 116 | XCTAssertTrue(reason.contains(aggregatedParentClass.value.name)) 117 | XCTAssertTrue(reason.contains("pV2")) 118 | XCTAssertTrue(reason.contains("pV1")) 119 | XCTAssertTrue(reason.contains(URL(fileURLWithPath: #file).path)) 120 | default: 121 | XCTFail() 122 | } 123 | } 124 | 125 | func test_execute_missingLeafVarDefinitions_verifyError() { 126 | // Splitting these expressions since Swift compiler thinks it's too complex. 127 | let childVars1 = grandParentAbstractVars + grandParentConcreteVars 128 | let childVars2 = parentAbstractVars + parentConcreteVars + parentAdditionalConcreteVars 129 | let childVars3 = childAbstractVars + childAdditionalConcreteVars 130 | let childMethods1 = grandParentAbstractMethods + grandParentConcreteMethods 131 | let childMethods2 = parentAbstractMethods + parentConcreteMethods + parentAdditionalConcreteMethods 132 | let childMethods3 = childAbstractMethods + childConcreteMethods + childAdditionalConcreteMethods 133 | let aggregatedChildClass = AggregatedConcreteSubclassDefinition(value: childConcreteClass, aggregatedVars: childVars1 + childVars2 + childVars3, aggregatedMethods: childMethods1 + childMethods2 + childMethods3) 134 | 135 | let task = ConcreteSubclassValidationTask(aggregatedConcreteSubclassDefinition: aggregatedChildClass) 136 | 137 | let result = try! task.execute() 138 | switch result { 139 | case .failureWithReason(let reason): 140 | XCTAssertTrue(reason.contains(aggregatedChildClass.value.name)) 141 | XCTAssertTrue(reason.contains("cV")) 142 | XCTAssertTrue(reason.contains(URL(fileURLWithPath: #file).path)) 143 | default: 144 | XCTFail() 145 | } 146 | } 147 | 148 | func test_execute_missingMiddleMethodDefinitions_verifyError() { 149 | // Splitting these expressions since Swift compiler thinks it's too complex. 150 | let parentVars1 = grandParentAbstractVars + grandParentConcreteVars 151 | let parentVars2 = parentAbstractVars + parentConcreteVars + parentAdditionalConcreteVars 152 | let parentMethods1 = grandParentAbstractMethods! 153 | let parentMethods2 = parentAbstractMethods + parentConcreteMethods + parentAdditionalConcreteMethods 154 | let aggregatedParentClass = AggregatedConcreteSubclassDefinition(value: parentConcreteClass, aggregatedVars: parentVars1 + parentVars2, aggregatedMethods: parentMethods1 + parentMethods2) 155 | 156 | let task = ConcreteSubclassValidationTask(aggregatedConcreteSubclassDefinition: aggregatedParentClass) 157 | 158 | let result = try! task.execute() 159 | switch result { 160 | case .failureWithReason(let reason): 161 | XCTAssertTrue(reason.contains(aggregatedParentClass.value.name)) 162 | XCTAssertTrue(reason.contains("gM1")) 163 | XCTAssertTrue(reason.contains("gM2(_:arg2:)")) 164 | XCTAssertTrue(reason.contains(URL(fileURLWithPath: #file).path)) 165 | default: 166 | XCTFail() 167 | } 168 | } 169 | 170 | func test_execute_missingLeafMethodDefinitions_verifyError() { 171 | // Splitting these expressions since Swift compiler thinks it's too complex. 172 | let childVars1 = grandParentAbstractVars + grandParentConcreteVars 173 | let childVars2 = parentAbstractVars + parentConcreteVars + parentAdditionalConcreteVars 174 | let childVars3 = childAbstractVars + childConcreteVars + childAdditionalConcreteVars 175 | let childMethods1 = grandParentAbstractMethods + grandParentConcreteMethods 176 | let childMethods2 = parentAbstractMethods + parentConcreteMethods + parentAdditionalConcreteMethods 177 | let childMethods3 = childAbstractMethods + childAdditionalConcreteMethods 178 | let aggregatedChildClass = AggregatedConcreteSubclassDefinition(value: childConcreteClass, aggregatedVars: childVars1 + childVars2 + childVars3, aggregatedMethods: childMethods1 + childMethods2 + childMethods3) 179 | 180 | let task = ConcreteSubclassValidationTask(aggregatedConcreteSubclassDefinition: aggregatedChildClass) 181 | 182 | let result = try! task.execute() 183 | switch result { 184 | case .failureWithReason(let reason): 185 | XCTAssertTrue(reason.contains(aggregatedChildClass.value.name)) 186 | XCTAssertTrue(reason.contains("cM(arg:)")) 187 | XCTAssertTrue(reason.contains(URL(fileURLWithPath: #file).path)) 188 | default: 189 | XCTFail() 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/DeclarationFilterTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassValidatorFramework 19 | 20 | class DeclarationFilterTaskTests: BaseFrameworkTests { 21 | 22 | func test_execute_noExclusion_noAbstractClass_verifyResult() { 23 | let url = fixtureUrl(for: "NoAbstractClass.swift") 24 | let task = DeclarationFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: []) 25 | 26 | let result = try! task.execute() 27 | 28 | switch result { 29 | case .shouldProcess(_, _): 30 | XCTFail() 31 | case .skip: 32 | break 33 | } 34 | } 35 | 36 | func test_execute_suffixExclusion_hasAbstractClass_verifyResult() { 37 | let url = fixtureUrl(for: "HasAbstractMethodClass.swift") 38 | let task = DeclarationFilterTask(url: url, exclusionSuffixes: ["MethodClass"], exclusionPaths: []) 39 | 40 | let result = try! task.execute() 41 | 42 | switch result { 43 | case .shouldProcess(_, _): 44 | XCTFail() 45 | case .skip: 46 | break 47 | } 48 | } 49 | 50 | func test_execute_pathExclusion_hasAbstractClass_verifyResult() { 51 | let url = fixtureUrl(for: "HasAbstractMethodClass.swift") 52 | let task = DeclarationFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: ["Fixtures/"]) 53 | 54 | let result = try! task.execute() 55 | 56 | switch result { 57 | case .shouldProcess(_, _): 58 | XCTFail() 59 | case .skip: 60 | break 61 | } 62 | } 63 | 64 | func test_execute_noExclusion_hasAbstractMethod_verifyResult() { 65 | let url = fixtureUrl(for: "HasAbstractMethodClass.swift") 66 | let task = DeclarationFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: []) 67 | 68 | let result = try! task.execute() 69 | 70 | switch result { 71 | case .shouldProcess(let processUrl, let content): 72 | XCTAssertEqual(processUrl, url) 73 | XCTAssertEqual(content, try! String(contentsOf: url)) 74 | case .skip: 75 | XCTFail() 76 | } 77 | } 78 | 79 | func test_execute_noExclusion_hasAbstractVar_verifyResult() { 80 | let url = fixtureUrl(for: "HasAbstractVarClass.swift") 81 | let task = DeclarationFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: []) 82 | 83 | let result = try! task.execute() 84 | 85 | switch result { 86 | case .shouldProcess(let processUrl, let content): 87 | XCTAssertEqual(processUrl, url) 88 | XCTAssertEqual(content, try! String(contentsOf: url)) 89 | case .skip: 90 | XCTFail() 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/DeclarationProducerTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassValidatorFramework 19 | 20 | class DeclarationProducerTaskTests: BaseFrameworkTests { 21 | 22 | func test_execute_noExclusion_fileWithMixedAbstractClasses_verifyResult() { 23 | let url = fixtureUrl(for: "MixedAbstractClasses.swift") 24 | let content = try! String(contentsOf: url) 25 | let task = DeclarationProducerTask(sourceUrl: url, sourceContent: content) 26 | 27 | let abstractClasses = try! task.execute() 28 | 29 | XCTAssertEqual(abstractClasses.count, 2) 30 | XCTAssertEqual(abstractClasses[0].name, "SAbstractVarClass") 31 | XCTAssertTrue(abstractClasses[0].methods.isEmpty) 32 | XCTAssertEqual(abstractClasses[0].vars.count, 1) 33 | XCTAssertEqual(abstractClasses[0].vars[0].name, "pProperty") 34 | 35 | XCTAssertEqual(abstractClasses[1].name, "KAbstractVarClass") 36 | XCTAssertEqual(abstractClasses[1].methods.count, 3) 37 | XCTAssertEqual(abstractClasses[1].methods[0].name, "someMethod()") 38 | 39 | XCTAssertEqual(abstractClasses[1].methods[1].name, "hahaMethod()") 40 | 41 | XCTAssertEqual(abstractClasses[1].methods[2].name, "paramMethod(_:b:)") 42 | 43 | XCTAssertEqual(abstractClasses[1].vars.count, 3) 44 | XCTAssertEqual(abstractClasses[1].vars[0].name, "someProperty") 45 | XCTAssertTrue(abstractClasses[1].vars[0].isAbstract) 46 | 47 | XCTAssertEqual(abstractClasses[1].vars[1].name, "nonAbstractProperty") 48 | XCTAssertFalse(abstractClasses[1].vars[1].isAbstract) 49 | 50 | XCTAssertEqual(abstractClasses[1].vars[2].name, "someBlahProperty") 51 | XCTAssertTrue(abstractClasses[1].vars[0].isAbstract) 52 | } 53 | 54 | func test_execute_noExclusion_noAbstractClass_verifyResult() { 55 | let url = fixtureUrl(for: "NoAbstractClass.swift") 56 | let content = try! String(contentsOf: url) 57 | let task = DeclarationProducerTask(sourceUrl: url, sourceContent: content) 58 | 59 | let abstractClasses = try! task.execute() 60 | 61 | XCTAssertTrue(abstractClasses.isEmpty) 62 | } 63 | 64 | func test_execute_noExclusion_fileWithInnerAbstractClasses_verifyResult() { 65 | let url = fixtureUrl(for: "NestedAbstractClasses.swift") 66 | let content = try! String(contentsOf: url) 67 | let task = DeclarationProducerTask(sourceUrl: url, sourceContent: content) 68 | 69 | let abstractClasses = try! task.execute() 70 | 71 | XCTAssertEqual(abstractClasses[0].name, "OutterAbstractClass") 72 | XCTAssertEqual(abstractClasses[0].methods.count, 2) 73 | XCTAssertFalse(abstractClasses[0].methods[0].isAbstract) 74 | XCTAssertEqual(abstractClasses[0].methods[0].name, "someMethod()") 75 | XCTAssertTrue(abstractClasses[0].methods[1].isAbstract) 76 | XCTAssertEqual(abstractClasses[0].methods[1].name, "paramMethod(_:b:)") 77 | XCTAssertEqual(abstractClasses[0].vars.count, 2) 78 | XCTAssertTrue(abstractClasses[0].vars[0].isAbstract) 79 | XCTAssertEqual(abstractClasses[0].vars[0].name, "someProperty") 80 | XCTAssertFalse(abstractClasses[0].vars[1].isAbstract) 81 | XCTAssertEqual(abstractClasses[0].vars[1].name, "nonAbstractProperty") 82 | 83 | XCTAssertEqual(abstractClasses[1].name, "InnerAbstractClassA") 84 | XCTAssertEqual(abstractClasses[1].methods.count, 1) 85 | XCTAssertTrue(abstractClasses[1].methods[0].isAbstract) 86 | XCTAssertEqual(abstractClasses[1].methods[0].name, "innerAbstractMethodA()") 87 | XCTAssertEqual(abstractClasses[1].vars.count, 0) 88 | 89 | XCTAssertEqual(abstractClasses[2].name, "InnerAbstractClassB") 90 | XCTAssertEqual(abstractClasses[2].methods.count, 1) 91 | XCTAssertTrue(abstractClasses[2].methods[0].isAbstract) 92 | XCTAssertEqual(abstractClasses[2].methods[0].name, "yoMethod()") 93 | XCTAssertEqual(abstractClasses[2].vars.count, 1) 94 | XCTAssertTrue(abstractClasses[2].vars[0].isAbstract) 95 | XCTAssertEqual(abstractClasses[2].vars[0].name, "innerAbstractVarB") 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/ExpressionCallUsageFilterTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassValidatorFramework 19 | 20 | class ExpressionCallUsageFilterTaskTests: BaseFrameworkTests { 21 | 22 | private var abstractClassDefinition: AbstractClassDefinition! 23 | 24 | override func setUp() { 25 | super.setUp() 26 | 27 | let abstractVarDefinition = VarDefinition(name: "abVar", isAbstract: true, isOverride: false) 28 | abstractClassDefinition = AbstractClassDefinition(name: "SomeAbstractC", vars: [abstractVarDefinition], methods: [], inheritedTypes: []) 29 | } 30 | 31 | func test_execute_noExclusion_noAbstractClass_verifyResult() { 32 | let url = fixtureUrl(for: "NoAbstractClass.swift") 33 | let task = ExpressionCallUsageFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: [], abstractClassDefinitions: []) 34 | 35 | let result = try! task.execute() 36 | 37 | switch result { 38 | case .shouldProcess(_, _): 39 | XCTFail() 40 | case .skip: 41 | break 42 | } 43 | } 44 | 45 | func test_execute_suffixExclusion_hasAbstractClass_verifyResult() { 46 | let url = fixtureUrl(for: "ViolateInstantiation.swift") 47 | let task = ExpressionCallUsageFilterTask(url: url, exclusionSuffixes: ["Instantiation"], exclusionPaths: [], abstractClassDefinitions: [abstractClassDefinition]) 48 | 49 | let result = try! task.execute() 50 | 51 | switch result { 52 | case .shouldProcess(_, _): 53 | XCTFail() 54 | case .skip: 55 | break 56 | } 57 | } 58 | 59 | func test_execute_pathExclusion_hasAbstractClass_verifyResult() { 60 | let url = fixtureUrl(for: "ViolateInstantiation.swift") 61 | let task = ExpressionCallUsageFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: ["Fixtures/"], abstractClassDefinitions: [abstractClassDefinition]) 62 | 63 | let result = try! task.execute() 64 | 65 | switch result { 66 | case .shouldProcess(_, _): 67 | XCTFail() 68 | case .skip: 69 | break 70 | } 71 | } 72 | 73 | func test_execute_noExclusion_hasAbstractSubclass_verifyResult() { 74 | let url = fixtureUrl(for: "ViolateInstantiation.swift") 75 | let task = ExpressionCallUsageFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: [], abstractClassDefinitions: [abstractClassDefinition]) 76 | 77 | let result = try! task.execute() 78 | 79 | switch result { 80 | case .shouldProcess(let processUrl, let content): 81 | XCTAssertEqual(processUrl, url) 82 | XCTAssertEqual(content, try! String(contentsOf: url)) 83 | case .skip: 84 | XCTFail() 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/ExpressionCallValidationTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SourceKittenFramework 18 | import SourceParsingFramework 19 | import XCTest 20 | @testable import AbstractClassValidatorFramework 21 | 22 | class ExpressionCallValidationTaskTests: BaseFrameworkTests { 23 | 24 | private var abstractClassDefinition: AbstractClassDefinition! 25 | 26 | override func setUp() { 27 | super.setUp() 28 | 29 | let abstractVarDefinition = VarDefinition(name: "abVar", isAbstract: true, isOverride: false) 30 | abstractClassDefinition = AbstractClassDefinition(name: "SomeAbstractC", vars: [abstractVarDefinition], methods: [], inheritedTypes: []) 31 | } 32 | 33 | func test_execute_noUsage_verifyResult() { 34 | let url = fixtureUrl(for: "NoAbstractClass.swift") 35 | let content = try! String(contentsOf: url) 36 | let task = ExpressionCallValidationTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: [abstractClassDefinition]) 37 | 38 | let result = try! task.execute() 39 | switch result { 40 | case .success: 41 | break 42 | default: 43 | XCTFail() 44 | } 45 | } 46 | 47 | func test_execute_hasSubclassUsage_noViolations() { 48 | let url = fixtureUrl(for: "UsageSubclass.swift") 49 | let content = try! String(contentsOf: url) 50 | let task = ExpressionCallValidationTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: [abstractClassDefinition]) 51 | 52 | let result = try! task.execute() 53 | switch result { 54 | case .success: 55 | break 56 | default: 57 | XCTFail() 58 | } 59 | } 60 | 61 | func test_execute_hasTypeUsage_noViolations() { 62 | let url = fixtureUrl(for: "UsageType.swift") 63 | let content = try! String(contentsOf: url) 64 | let task = ExpressionCallValidationTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: [abstractClassDefinition]) 65 | 66 | let result = try! task.execute() 67 | switch result { 68 | case .success: 69 | break 70 | default: 71 | XCTFail() 72 | } 73 | } 74 | 75 | func test_execute_hasIViolations() { 76 | let url = fixtureUrl(for: "ViolateInstantiation.swift") 77 | let content = try! String(contentsOf: url) 78 | let task = ExpressionCallValidationTask(sourceUrl: url, sourceContent: content, abstractClassDefinitions: [abstractClassDefinition]) 79 | 80 | let result = try! task.execute() 81 | switch result { 82 | case .failureWithReason(let reason): 83 | XCTAssertTrue(reason.contains(abstractClassDefinition.name)) 84 | XCTAssertTrue(reason.contains(url.path)) 85 | default: 86 | XCTFail() 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/ConcreteSubclass.swift: -------------------------------------------------------------------------------- 1 | class ConcreteClass1: ParentAbstractClass, AProtocol { 2 | var someProperty: SomeAbstractC { 3 | return SomeAbstractC(blah: Blah(), kaa: BB()) 4 | } 5 | 6 | override var grandParentVar: GrandParentVar { 7 | return GrandParentVar(child: self) 8 | } 9 | 10 | override func parentMethod(index: Int) -> String { 11 | return "concrete" 12 | } 13 | } 14 | 15 | class ConcreteClass2: GrandParentAbstractClass { 16 | override var grandParentVar: GrandParentVar { 17 | return GrandParentVar(child: self) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/GenericSuperConcreteSubclass.swift: -------------------------------------------------------------------------------- 1 | class ConcreteClassGenericType: GenericBaseClass, AProtocol { 2 | 3 | override var genericTypeVar: String { 4 | return "var" 5 | } 6 | 7 | override func returnGenericType() -> String { 8 | return "" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/HasAbstractMethodClass.swift: -------------------------------------------------------------------------------- 1 | class SomeAbstractMethodClass: AbstractClass { 2 | 3 | func someAbstractMethod() -> Bar { 4 | abstractMethod() 5 | } 6 | 7 | var someProperty: Int { 8 | return 1 9 | } 10 | 11 | func anotherAbstractMethod() -> String { 12 | abstractMethod() 13 | } 14 | } 15 | 16 | class RandomClassB: Rand { 17 | var r: Int { 18 | return 123 19 | } 20 | func rd() -> Object { 21 | return super.rdd() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/HasAbstractVarClass.swift: -------------------------------------------------------------------------------- 1 | class RandomClassA { 2 | func randomMethod() { 3 | 4 | } 5 | } 6 | 7 | class SomeAbstractVarClass: AbstractClass { 8 | var someProperty: Int { 9 | abstractMethod() 10 | } 11 | 12 | var nonAbstractProperty: String { 13 | return "haha" 14 | } 15 | 16 | func someMethod() -> String { 17 | return "" 18 | } 19 | 20 | var anotherNonAbstractProperty: Object { 21 | return someMethodObj() 22 | } 23 | 24 | var someBlahProperty: Blah { 25 | abstractMethod() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/InnerConcreteSubclass.swift: -------------------------------------------------------------------------------- 1 | class AnOutterClass { 2 | var someProperty: SomeAbstractC { 3 | return SomeAbstractC(blah: Blah(), kaa: BB()) 4 | } 5 | 6 | func someMethod(index: Int) -> String { 7 | class ConcreteClass2: GrandParentAbstractClass { 8 | override var grandParentVar: GrandParentVar { 9 | return GrandParentVar(child: self) 10 | } 11 | } 12 | return "concrete" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/ExpressionCall/AbstractInstantiation.swift: -------------------------------------------------------------------------------- 1 | class BadInstantiation { 2 | 3 | var child: ChildAbstract { 4 | return ChildAbstract() 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/ExpressionCall/GrandParentAbstract.swift: -------------------------------------------------------------------------------- 1 | class GrandParentAbstract: AbstractClass { 2 | var gpAbstractVar: GPVar { 3 | abstractMethod() 4 | } 5 | 6 | let gpLet = "haha" 7 | 8 | var gpConcreteVar: Int { 9 | return 21 10 | } 11 | 12 | func gpConcreteMethod() -> String { 13 | return "blah" 14 | } 15 | 16 | func gpAbstractMethod() -> GPMethod { 17 | abstractMethod() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/ExpressionCall/ParentAbstractChildAbstractNested.swift: -------------------------------------------------------------------------------- 1 | class ParentAbstract: GrandParentAbstract { 2 | 3 | var pConcreteVar: PVar { 4 | return PVar() 5 | } 6 | 7 | func pConcreteMethod() -> Blah { 8 | return Blah() 9 | } 10 | 11 | func pAbstractMethod(arg1: Int) -> PMethod { 12 | abstractMethod() 13 | } 14 | 15 | class ChildAbstract: ParentAbstract { 16 | var cAbstractVar: CVar { 17 | abstractMethod() 18 | } 19 | 20 | var cConcreteVar: ChildConcrete { 21 | return ChildConcrete() 22 | } 23 | 24 | func cAbstractMethod(_ a: Arg1, b: ArgB) -> CMethod { 25 | abstractMethod() 26 | } 27 | 28 | func cConcreteMethod() -> Int { 29 | return 12 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/GenericSuperclass/ChildConcrete.swift: -------------------------------------------------------------------------------- 1 | class ChildConcrete: ChildAbstract, BlahProtocol { 2 | 3 | override var gpAbstractVar: GPVar { 4 | return GPVar() 5 | } 6 | 7 | override func pAbstractMethod(arg1: Int) -> Int { 8 | return PMethod(arg: arg1) 9 | } 10 | 11 | override func cAbstractMethod(_ a: String, b: String) -> String { 12 | return CMethod(a: a, b: b) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/GenericSuperclass/GrandParentAbstract.swift: -------------------------------------------------------------------------------- 1 | class GrandParentAbstract: AbstractClass { 2 | var gpAbstractVar: GPVar { 3 | abstractMethod() 4 | } 5 | 6 | let gpLet = "haha" 7 | 8 | var gpConcreteVar: Int { 9 | return 21 10 | } 11 | 12 | func gpConcreteMethod() -> String { 13 | return "blah" 14 | } 15 | 16 | func gpAbstractMethod() -> GPMethod { 17 | abstractMethod() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/GenericSuperclass/ParentAbstractChildAbstractNested.swift: -------------------------------------------------------------------------------- 1 | class ParentAbstract: GrandParentAbstract { 2 | 3 | var pConcreteVar: PVar { 4 | return PVar() 5 | } 6 | 7 | func pConcreteMethod() -> Blah { 8 | return Blah() 9 | } 10 | 11 | func pAbstractMethod(arg1: T) -> T { 12 | abstractMethod() 13 | } 14 | 15 | class ChildAbstract: ParentAbstract

{ 16 | var cAbstractVar: CVar { 17 | abstractMethod() 18 | } 19 | 20 | var cConcreteVar: ChildConcrete { 21 | return ChildConcrete() 22 | } 23 | 24 | func cAbstractMethod(_ a: T, b: T) -> T { 25 | abstractMethod() 26 | } 27 | 28 | func cConcreteMethod() -> Int { 29 | return 12 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/Implementation/ChildConcrete.swift: -------------------------------------------------------------------------------- 1 | class ChildConcrete: ChildAbstract { 2 | 3 | override var gpAbstractVar: GPVar { 4 | return GPVar() 5 | } 6 | 7 | override func pAbstractMethod(arg1: Int) -> PMethod { 8 | return PMethod(arg: arg1) 9 | } 10 | 11 | override func cAbstractMethod(_ a: Arg1, b: ArgB) -> CMethod { 12 | return CMethod(a: a, b: b) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/Implementation/GrandParentAbstract.swift: -------------------------------------------------------------------------------- 1 | class GrandParentAbstract: AbstractClass { 2 | var gpAbstractVar: GPVar { 3 | abstractMethod() 4 | } 5 | 6 | let gpLet = "haha" 7 | 8 | var gpConcreteVar: Int { 9 | return 21 10 | } 11 | 12 | func gpConcreteMethod() -> String { 13 | return "blah" 14 | } 15 | 16 | func gpAbstractMethod() -> GPMethod { 17 | abstractMethod() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Invalid/Implementation/ParentAbstractChildAbstractNested.swift: -------------------------------------------------------------------------------- 1 | class ParentAbstract: GrandParentAbstract { 2 | 3 | var pConcreteVar: PVar { 4 | return PVar() 5 | } 6 | 7 | func pConcreteMethod() -> Blah { 8 | return Blah() 9 | } 10 | 11 | func pAbstractMethod(arg1: Int) -> PMethod { 12 | abstractMethod() 13 | } 14 | 15 | class ChildAbstract: ParentAbstract { 16 | var cAbstractVar: CVar { 17 | abstractMethod() 18 | } 19 | 20 | var cConcreteVar: ChildConcrete { 21 | return ChildConcrete() 22 | } 23 | 24 | func cAbstractMethod(_ a: Arg1, b: ArgB) -> CMethod { 25 | abstractMethod() 26 | } 27 | 28 | func cConcreteMethod() -> Int { 29 | return 12 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Valid/ChildConcrete.swift: -------------------------------------------------------------------------------- 1 | class ChildConcrete: ChildAbstract { 2 | 3 | override var gpAbstractVar: GPVar { 4 | return GPVar() 5 | } 6 | 7 | override var cAbstractVar: CVar { 8 | return CVar() 9 | } 10 | 11 | override func gpAbstractMethod() -> GPMethod { 12 | return GPMethod() 13 | } 14 | 15 | override func pAbstractMethod(arg1: Int) -> PMethod { 16 | return PMethod(arg: arg1) 17 | } 18 | 19 | override func cAbstractMethod(_ a: Arg1, b: ArgB) -> CMethod { 20 | return CMethod(a: a, b: b) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Valid/GrandParentAbstract.swift: -------------------------------------------------------------------------------- 1 | class GrandParentAbstract: AbstractClass { 2 | var gpAbstractVar: GPVar { 3 | abstractMethod() 4 | } 5 | 6 | let gpLet = "haha" 7 | 8 | var gpConcreteVar: Int { 9 | return 21 10 | } 11 | 12 | func gpConcreteMethod() -> String { 13 | return "blah" 14 | } 15 | 16 | func gpAbstractMethod() -> GPMethod { 17 | abstractMethod() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/Integration/Valid/ParentAbstractChildAbstractNested.swift: -------------------------------------------------------------------------------- 1 | class ParentAbstract: GrandParentAbstract { 2 | 3 | var pConcreteVar: PVar { 4 | return PVar() 5 | } 6 | 7 | func pConcreteMethod() -> Blah { 8 | return Blah() 9 | } 10 | 11 | func pAbstractMethod(arg1: Int) -> PMethod { 12 | abstractMethod() 13 | } 14 | 15 | class ChildAbstract: ParentAbstract { 16 | var cAbstractVar: CVar { 17 | abstractMethod() 18 | } 19 | 20 | var cConcreteVar: ChildConcrete { 21 | return ChildConcrete() 22 | } 23 | 24 | func cAbstractMethod(_ a: Arg1, b: ArgB) -> CMethod { 25 | abstractMethod() 26 | } 27 | 28 | func cConcreteMethod() -> Int { 29 | return 12 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/MiddleAbstractClass.swift: -------------------------------------------------------------------------------- 1 | class MiddleAbstractClass: GrandParentAbstractClass { 2 | var someProperty: SomeAbstractC { 3 | return SomeAbstractC(blah: Blah(), kaa: BB()) 4 | } 5 | 6 | func someMethod() -> String { 7 | abstractMethod() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/MixedAbstractClasses.swift: -------------------------------------------------------------------------------- 1 | class RandomClassA { 2 | func randomMethod() { 3 | 4 | } 5 | } 6 | 7 | class SAbstractVarClass: AbstractClass { 8 | 9 | var pProperty: Object { 10 | abstractMethod() 11 | } 12 | } 13 | 14 | class KAbstractVarClass: AbstractClass { 15 | var someProperty: Int { 16 | abstractMethod() 17 | } 18 | 19 | var nonAbstractProperty: String { 20 | return "haha" 21 | } 22 | 23 | func someMethod() -> String { 24 | return "" 25 | } 26 | 27 | func hahaMethod() -> Haha { 28 | abstractMethod() 29 | } 30 | 31 | func paramMethod(_ a: HJJ, b: Bar) -> PPP { 32 | abstractMethod() 33 | } 34 | 35 | var someBlahProperty: Blah { 36 | abstractMethod() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/NestedAbstractClasses.swift: -------------------------------------------------------------------------------- 1 | class RandomClassA { 2 | func randomMethod() { 3 | 4 | } 5 | } 6 | 7 | class OutterAbstractClass: AbstractClass { 8 | var someProperty: Int { 9 | abstractMethod() 10 | } 11 | 12 | var nonAbstractProperty: String { 13 | class InnerAbstractClassA: AbstractClass { 14 | func innerAbstractMethodA() { 15 | abstractMethod() 16 | } 17 | } 18 | return "haha" 19 | } 20 | 21 | func someMethod() -> String { 22 | class InnerAbstractClassB: AbstractClass { 23 | var innerAbstractVarB: Int { 24 | abstractMethod() 25 | } 26 | func yoMethod() -> Yo { 27 | abstractMethod() 28 | } 29 | } 30 | return "" 31 | } 32 | 33 | func paramMethod(_ a: HJJ, b: Bar) -> PPP { 34 | abstractMethod() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/NoAbstractClass.swift: -------------------------------------------------------------------------------- 1 | class SomeConcreteClass: ParentClass { 2 | var someProperty: Int { 3 | return 1 4 | } 5 | 6 | func someMethod() -> String { 7 | return "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/UsageSubclass.swift: -------------------------------------------------------------------------------- 1 | class AConcreteClass: SomeAbstractC { 2 | var someProperty: Int { 3 | return 1 4 | } 5 | 6 | func someMethod() -> String { 7 | return "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/UsageType.swift: -------------------------------------------------------------------------------- 1 | class BConcreteClass { 2 | var someProperty: SomeAbstractC { 3 | return Blah() 4 | } 5 | 6 | func someMethod() -> String { 7 | return "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/Fixtures/ViolateInstantiation.swift: -------------------------------------------------------------------------------- 1 | class ViolationClass { 2 | var someProperty: SomeAbstractC { 3 | return SomeAbstractC(blah: Blah(), kaa: BB()) 4 | } 5 | 6 | func someMethod() -> String { 7 | return "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/SubclassUsageFilterTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import AbstractClassValidatorFramework 19 | 20 | class SubclassUsageFilterTaskTests: BaseFrameworkTests { 21 | 22 | func test_execute_noExclusion_noAbstractClass_verifyResult() { 23 | let url = fixtureUrl(for: "NoAbstractClass.swift") 24 | let task = SubclassUsageFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: [], abstractClassDefinitions: []) 25 | 26 | let result = try! task.execute() 27 | 28 | switch result { 29 | case .shouldProcess(_, _): 30 | XCTFail() 31 | case .skip: 32 | break 33 | } 34 | } 35 | 36 | func test_execute_suffixExclusion_hasAbstractClass_verifyResult() { 37 | let url = fixtureUrl(for: "ConcreteSubclass.swift") 38 | let task = SubclassUsageFilterTask(url: url, exclusionSuffixes: ["Subclass"], exclusionPaths: [], abstractClassDefinitions: [AbstractClassDefinition(name: "ParentAbstractClass", vars: [], methods: [], inheritedTypes: [])]) 39 | 40 | let result = try! task.execute() 41 | 42 | switch result { 43 | case .shouldProcess(_, _): 44 | XCTFail() 45 | case .skip: 46 | break 47 | } 48 | } 49 | 50 | func test_execute_pathExclusion_hasAbstractClass_verifyResult() { 51 | let url = fixtureUrl(for: "ConcreteSubclass.swift") 52 | let task = SubclassUsageFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: ["Fixtures/"], abstractClassDefinitions: [AbstractClassDefinition(name: "ParentAbstractClass", vars: [], methods: [], inheritedTypes: [])]) 53 | 54 | let result = try! task.execute() 55 | 56 | switch result { 57 | case .shouldProcess(_, _): 58 | XCTFail() 59 | case .skip: 60 | break 61 | } 62 | } 63 | 64 | func test_execute_noExclusion_simpleSubclass_verifyResult() { 65 | let url = fixtureUrl(for: "ConcreteSubclass.swift") 66 | let task = SubclassUsageFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: [], abstractClassDefinitions: [AbstractClassDefinition(name: "ParentAbstractClass", vars: [], methods: [], inheritedTypes: [])]) 67 | 68 | let result = try! task.execute() 69 | 70 | switch result { 71 | case .shouldProcess(let processUrl, let content): 72 | XCTAssertEqual(processUrl, url) 73 | XCTAssertEqual(content, try! String(contentsOf: url)) 74 | case .skip: 75 | XCTFail() 76 | } 77 | } 78 | 79 | func test_execute_noExclusion_genericSubclass_verifyResult() { 80 | let url = fixtureUrl(for: "GenericSuperConcreteSubclass.swift") 81 | let task = SubclassUsageFilterTask(url: url, exclusionSuffixes: [], exclusionPaths: [], abstractClassDefinitions: [AbstractClassDefinition(name: "GenericBaseClass", vars: [], methods: [], inheritedTypes: [])]) 82 | 83 | let result = try! task.execute() 84 | 85 | switch result { 86 | case .shouldProcess(let processUrl, let content): 87 | XCTAssertEqual(processUrl, url) 88 | XCTAssertEqual(content, try! String(contentsOf: url)) 89 | case .skip: 90 | XCTFail() 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Validator/Tests/AbstractClassValidatorFrameworkTests/ValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SourceKittenFramework 18 | import SourceParsingFramework 19 | import XCTest 20 | @testable import AbstractClassValidatorFramework 21 | 22 | class ValidatorTests: BaseFrameworkTests { 23 | 24 | func test_validate_noExclusions_withNoViolations_verifySuccess() { 25 | let sourceRoot = fixtureUrl(for: "/Integration/Valid/") 26 | 27 | try! Validator().validate(from: [sourceRoot.path], excludingFilesEndingWith: [], excludingFilesWithPaths: [], shouldCollectParsingInfo: false, timeout: 10, concurrencyLimit: nil) 28 | } 29 | 30 | func test_validate_exclusionPath_withViolations_verifySuccess() { 31 | let sourceRoot = fixtureUrl(for: "/Integration/Invalid/") 32 | 33 | try! Validator().validate(from: [sourceRoot.path], excludingFilesEndingWith: [], excludingFilesWithPaths: ["/Integration"], shouldCollectParsingInfo: false, timeout: 10, concurrencyLimit: nil) 34 | } 35 | 36 | func test_validate_exclusionSuffix_withViolations_verifySuccess() { 37 | let sourceRoot = fixtureUrl(for: "/Integration/Invalid/") 38 | 39 | try! Validator().validate(from: [sourceRoot.path], excludingFilesEndingWith: ["Concrete", "Instantiation"], excludingFilesWithPaths: [], shouldCollectParsingInfo: false, timeout: 10, concurrencyLimit: nil) 40 | } 41 | 42 | func test_validate_noExclusions_withExpressionCallViolations_verifyError() { 43 | let sourceRoot = fixtureUrl(for: "/Integration/Invalid/ExpressionCall/") 44 | 45 | do { 46 | try Validator().validate(from: [sourceRoot.path], excludingFilesEndingWith: [], excludingFilesWithPaths: [], shouldCollectParsingInfo: false, timeout: 10, concurrencyLimit: nil) 47 | XCTFail() 48 | } catch GenericError.withMessage(let message) { 49 | XCTAssertTrue(message.contains("ChildAbstract")) 50 | XCTAssertTrue(message.contains("/Fixtures/Integration/Invalid/ExpressionCall/AbstractInstantiation.swift")) 51 | } catch { 52 | XCTFail() 53 | } 54 | } 55 | 56 | func test_validate_noExclusions_withImplementationViolations_verifyError() { 57 | let sourceRoot = fixtureUrl(for: "/Integration/Invalid/Implementation/") 58 | 59 | do { 60 | try Validator().validate(from: [sourceRoot.path], excludingFilesEndingWith: [], excludingFilesWithPaths: [], shouldCollectParsingInfo: false, timeout: 10, concurrencyLimit: nil) 61 | XCTFail() 62 | } catch GenericError.withMessage(let message) { 63 | XCTAssertTrue(message.contains("ChildConcrete")) 64 | XCTAssertTrue(message.contains("cAbstractVar")) 65 | } catch { 66 | XCTFail() 67 | } 68 | } 69 | 70 | func test_validate_noExclusions_withGenericSuperclassImplementationViolations_verifyError() { 71 | let sourceRoot = fixtureUrl(for: "/Integration/Invalid/GenericSuperclass/") 72 | 73 | do { 74 | try Validator().validate(from: [sourceRoot.path], excludingFilesEndingWith: [], excludingFilesWithPaths: [], shouldCollectParsingInfo: false, timeout: 10, concurrencyLimit: nil) 75 | XCTFail() 76 | } catch GenericError.withMessage(let message) { 77 | XCTAssertTrue(message.contains("ChildConcrete")) 78 | XCTAssertTrue(message.contains("cAbstractVar")) 79 | } catch { 80 | XCTFail() 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Validator/bin/abstractclassvalidator: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/swift-abstract-class/0a4a83d9b0b91ad8228e50de39122e44775c722a/Validator/bin/abstractclassvalidator -------------------------------------------------------------------------------- /Validator/xcode.xcconfig: -------------------------------------------------------------------------------- 1 | OTHER_SWIFT_FLAGS[config=Debug] = -DDEBUG 2 | -------------------------------------------------------------------------------- /foundation.xcconfig: -------------------------------------------------------------------------------- 1 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 2 | --------------------------------------------------------------------------------