Skip to content

Commit

Permalink
Merge pull request #107 from silverleague/support_short_names
Browse files Browse the repository at this point in the history
Support short names
  • Loading branch information
robbieaverill committed Jul 5, 2018
2 parents d81f0e1 + 6ac87bf commit b4a79c8
Show file tree
Hide file tree
Showing 32 changed files with 695 additions and 251 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Expand Up @@ -12,7 +12,7 @@ matrix:
- php: 5.6
env: DB=MYSQL PHPUNIT_TEST=1
- php: 7.1
env: DB=MYSQL PHPCS_TEST=1 PHPUNIT_TEST=1 PHPUNIT_COVERAGE_TEST=1
env: DB=MYSQL PHPCS_TEST=1 PHPUNIT_COVERAGE_TEST=1

before_script:
- phpenv rehash
Expand All @@ -23,11 +23,12 @@ before_script:
- composer install --prefer-dist
- composer require --prefer-dist --no-update silverstripe/recipe-core:1.0.x-dev
- composer require --prefer-dist --no-update squizlabs/php_codesniffer:*
- vendor/bin/sake dev/build flush=all

script:
- if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit ideannotator/tests/; fi
- if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi
- if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=vendor/silverstripe/framework/phpcs.xml.dist ideannotator/src/ ; fi
- if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=ideannotator/phpcs.xml.dist ideannotator/src/ ; fi

after_success:
- if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi
Expand Down
9 changes: 9 additions & 0 deletions Changelog.md
Expand Up @@ -33,3 +33,12 @@

# 3.0 beta-1
* Support for SilverStripe 4

# 3.0 rc-1
* Updated support for SilverStripe 4
* Added support for the `through` method

# 3.0 rc-2
* Fixed bug trimming too much whitespace in rc-1
* Support for short classnames instead of FQN
* ~~require_once in tests no longer needed~~ Nope, still needed
2 changes: 1 addition & 1 deletion LICENSE.md
@@ -1,4 +1,4 @@
Copyright (c) 2015, Martijn van Nieuwenhoven
Copyright (c) 2015-2018, Martijn van Nieuwenhoven/SilverLeague
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Expand Down
2 changes: 1 addition & 1 deletion _config/config.yml
Expand Up @@ -3,7 +3,7 @@ Name: ideannotator
---
SilverStripe\Dev\DevBuildController:
extensions:
- SilverLeague\IDEAnnotator\Annotatable
- SilverLeague\IDEAnnotator\Extensions\Annotatable
SilverLeague\IDEAnnotator\DataObjectAnnotator:
enabled_modules:
- mysite
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -29,6 +29,7 @@
"require-dev": {
"scriptfusion/phpunit-immediate-exception-printer": "^1",
"phpunit/phpunit": "^5.7",
"silverstripe/recipe-cms": "~1",
"squizlabs/php_codesniffer": "3.*"
},
"autoload": {
Expand Down
21 changes: 21 additions & 0 deletions docs/en/Installation.md
Expand Up @@ -53,3 +53,24 @@ SilverLeague\IDEAnnotator\DataObjectAnnotator:
- mysite
- SilverLeague/IDEAnnotator
```

If you don't want to use fully qualified classnames, you can configure that like so:

```yaml

---
Only:
environment: 'dev'
---
SilverLeague\IDEAnnotator\DataObjectAnnotator:
enabled: true
use_short_name: true
enabled_modules:
- mysite
```

**NOTE**

- Using short names, will also shorten core names like `ManyManyList`, you'll have to adjust your use statements to work.

- If you change the usage of short names halfway in your project, you may need to clear out all your docblocks before regenerating
31 changes: 31 additions & 0 deletions phpcs.xml.dist
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>

<!-- base rules are PSR-2 -->
<rule ref="PSR2">
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName"/>
<exclude name="PSR1.Files.SideEffects.FoundWithSymbols"/>
<exclude name="PSR2.Classes.PropertyDeclaration"/>
<exclude name="PSR2.ControlStructures.SwitchDeclaration"/> <!-- causes php notice while linting -->
<exclude name="PSR2.ControlStructures.SwitchDeclaration.WrongOpenercase"/>
<exclude name="PSR2.ControlStructures.SwitchDeclaration.WrongOpenerdefault"/>
<exclude name="PSR2.ControlStructures.SwitchDeclaration.TerminatingComment"/>
<exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
<exclude name="Squiz.Scope.MethodScope"/>
<exclude name="Squiz.Classes.ValidClassName.NotCamelCaps"/>
<exclude name="Generic.Files.LineLength.TooLong"/>
<exclude name="PEAR.Functions.ValidDefaultValue.NotAtEnd"/>
</rule>
<rule phpcbf-only="true" ref="Squiz.Strings.ConcatenationSpacing">
<properties>
<property name="spacing" value="1"/>
<property name="ignoreNewlines" value="true"/>
</properties>
</rule>

<!-- include php files only -->
<arg name="extensions" value="php,lib,inc,php5"/>
</ruleset>

134 changes: 112 additions & 22 deletions src/DataObjectAnnotator.php
Expand Up @@ -6,18 +6,24 @@
use LogicException;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionException;
use SilverLeague\IDEAnnotator\Generators\DocBlockGenerator;
use SilverLeague\IDEAnnotator\Helpers\AnnotateClassInfo;
use SilverLeague\IDEAnnotator\Helpers\AnnotatePermissionChecker;
use SilverStripe\Control\Director;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DB;
use stdClass;

/**
* Class DataObjectAnnotator
* Generates phpdoc annotations for database fields and orm relations
* so IDE's with autocompletion and property inspection will recognize properties and relation methods.
* so IDE's with autocompletion and property inspection will recognize properties
* and relation methods.
*
* The annotations can be generated with dev/build with @see Annotatable
* and from the @see DataObjectAnnotatorTask
Expand All @@ -34,16 +40,26 @@ class DataObjectAnnotator
use Configurable;
use Extensible;

/**
* All classes that subclass Object
*
* @var array
*/
protected static $extension_classes = [];

/**
* @config
* Enable generation from @see Annotatable and @see DataObjectAnnotatorTask
*
* @var bool
*/
private static $enabled = false;

/**
* @config
* Enable modules that are allowed to have generated docblocks for DataObjects and DataExtensions
* Enable modules that are allowed to have generated docblocks for
* DataObjects and DataExtensions
*
* @var array
*/
private static $enabled_modules = ['mysite'];
Expand All @@ -60,21 +76,67 @@ class DataObjectAnnotator

/**
* DataObjectAnnotator constructor.
*
* @throws NotFoundExceptionInterface
* @throws ReflectionException
*/
public function __construct()
{
// Don't instantiate anything if annotations are not enabled.
if (static::config()->get('enabled') === true && Director::isDev()) {
$this->extend('beforeDataObjectAnnotator');

$this->setupExtensionClasses();

$this->permissionChecker = Injector::inst()->get(AnnotatePermissionChecker::class);

foreach ($this->permissionChecker->getSupportedParentClasses() as $supportedParentClass) {
$this->setEnabledClasses($supportedParentClass);
}

$this->extend('afterDataObjectAnnotator');
}
}

/**
* Named `setup` to not clash with the actual setter
*
* Loop all extendable classes and see if they actually have extensions
* If they do, add it to the array
* Clean up the array of duplicates
* Then save the setup of the classes in a static array, this is to save memory
*
* @throws ReflectionException
*/
protected function setupExtensionClasses()
{
$extension_classes = [];

$extendableClasses = Config::inst()->getAll();
// We need to check all config to see if the class is extensible
// @todo change this to a proper php array_walk or something method?
foreach ($extendableClasses as $key => $configClass) {
// If the class doesn't already exist in the extension classes
// And the 'extensions' key is set in the config class
// And the 'extensions' key actually contains values
// Add it.
if (!in_array(self::$extension_classes, $configClass, true) &&
isset($configClass['extensions']) &&
count($configClass['extensions']) > 0
) {
$extension_classes[] = ClassInfo::class_name($key);
}
}

$extension_classes = array_unique($extension_classes);

static::$extension_classes = $extension_classes;
}

/**
* Get all annotatable classes from enabled modules
* @param string|StdClass $supportedParentClass
* @throws ReflectionException
*/
protected function setEnabledClasses($supportedParentClass)
{
Expand All @@ -86,6 +148,36 @@ protected function setEnabledClasses($supportedParentClass)
}
}

/**
* @return array
*/
public static function getExtensionClasses()
{
return self::$extension_classes;
}

/**
* @param array $extension_classes
*/
public static function setExtensionClasses($extension_classes)
{
self::$extension_classes = $extension_classes;
}

/**
* Add another extension class
* False checking, because what we get might be uppercase and then lowercase
* Allowing for duplicates here, to clean up later
*
* @param string $extension_class
*/
public static function pushExtensionClass($extension_class)
{
if (!in_array($extension_class, self::$extension_classes)) {
self::$extension_classes[] = $extension_class;
}
}

/**
* @return boolean
*/
Expand Down Expand Up @@ -142,6 +234,7 @@ public function getClassesForModule($moduleName)
*
* @param string $className
* @return bool
* @throws \InvalidArgumentException
* @throws ReflectionException
* @throws NotFoundExceptionInterface
*/
Expand All @@ -168,18 +261,18 @@ protected function writeFileContent($className)
$filePath = $classInfo->getClassFilePath();

if (!is_writable($filePath)) {
// Unsure how to test this properly
DB::alteration_message($className . ' is not writable by ' . get_current_user(), 'error');
} else {
$original = file_get_contents($filePath);
$generated = $this->getGeneratedFileContent($original, $className);

// we have a change, so write the new file
if ($generated && $generated !== $original && $className) {
// Trim unneeded whitespaces at the end of lines
$generated = preg_replace('/\s+$/m', '', $generated);
file_put_contents($filePath, $generated);
DB::alteration_message($className . ' Annotated', 'created');
} elseif ($generated === $original && $className) {
// Unsure how to test this properly
DB::alteration_message($className, 'repaired');
}
}
Expand All @@ -202,28 +295,25 @@ protected function getGeneratedFileContent($fileContent, $className)
$existing = $generator->getExistingDocBlock();
$generated = $generator->getGeneratedDocBlock();

if ($existing) {
// Trim unneeded whitespaces at the end of lines for PSR-2
$generated = preg_replace('/\s+$/m', '', $generated);

// $existing could be a boolean that in theory is `true`
// It never is though (according to the generator's doc)
if ((bool)$existing !== false) {
$fileContent = str_replace($existing, $generated, $fileContent);
} else {
$needle = "class {$className}";
$replace = "{$generated}\nclass {$className}";
$pos = strpos($fileContent, $needle);
if ($pos !== false) {
if (class_exists($className)) {
$exploded = ClassInfo::shortName($className);
$needle = "class {$exploded}";
$replace = "{$generated}\nclass {$exploded}";
$pos = strpos($fileContent, $needle);
$fileContent = substr_replace($fileContent, $replace, $pos, strlen($needle));
} else {
if (strrpos($className, "\\") !== false && class_exists($className)) {
$exploded = explode("\\", $className);
$classNameNew = end($exploded);
$needle = "class {$classNameNew}";
$replace = "{$generated}\nclass {$classNameNew}";
$pos = strpos($fileContent, $needle);
$fileContent = substr_replace($fileContent, $replace, $pos, strlen($needle));
} else {
DB::alteration_message(
"Could not find string 'class $className'. Please check casing and whitespace.",
'error'
);
}
DB::alteration_message(
"Could not find string 'class $className'. Please check casing and whitespace.",
'error'
);
}
}

Expand Down

0 comments on commit b4a79c8

Please sign in to comment.