Skip to content

Commit

Permalink
Merge pull request #4 from tianyiw2013/php81-1
Browse files Browse the repository at this point in the history
Merge upstream
  • Loading branch information
tianyiw2013 committed Feb 11, 2022
2 parents 5cb3915 + 40f1b46 commit 4356c9c
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 17 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -3,9 +3,15 @@
All notable changes to the "php-docblocker" extension will be documented in this file.

## [Unreleased]
- Allow configuration of default type
- Fixed fully qualifies class namespace detection can be incorrect with _ [#214](https://github.com/neild3r/vscode-php-docblocker/issues/214)
- Supported fully qualifies class namespace use with bracket `use some\namespace\{ ClassA, ClassB, ... }`
- Supported fully qualifies class namespace use with comma `use some\namespace\ClassA, some\namespace\ClassB, ...`
- Fix issue with coverage reports not firing
- Supported PHP 8.1 Readonly Properties
- Supported PHP 8.1 Intersection Types


## [2.6.1] - 2021-10-12
- Fix double start delimeter when vscode setting `editor.autoClosingBrackets` is set to `never`
- Improve class head tolerance when there isn't one or it's too long
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -9,7 +9,6 @@ We now have a set of unit tests and some full coverage on the parsing of signatu
## Features

* Completion snippet after `/**` above a class, function, class property
* Continuation of DocBlock when pressing enter when in a DocBlock
* Completion of DocBlock tags such as `@param`, `@return`, `@throws`
* Inferring of param and return types from signatures
* Configuration of template for each type of docblock completion
Expand All @@ -25,6 +24,7 @@ This extension contributes the following settings:
* `php-docblocker.gap`: set to `false` to disable the gap between the description and tags
* `php-docblocker.returnGap`: set to `true` to add a gap between the param and return tags
* `php-docblocker.returnVoid`: set to `false` to turn off the automatic void return type when it can't be determined
* `php-docblocker.defaultType`: default type to use if a type wasn't able to to be determined
* `php-docblocker.extra`: an array of extra tags to add to each DocBlock (These can include tabstops and snippet variables)
* `php-docblocker.useShortNames`: Whether we should use short type names. e.g. bool or boolean
* `php-docblocker.qualifyClassNames`: When adding type hints for class names search namespace use statements and qualify the class
Expand Down
8 changes: 7 additions & 1 deletion package.json
Expand Up @@ -61,6 +61,11 @@
"default": false,
"description": "If there should be a gap between params and return"
},
"php-docblocker.defaultType": {
"type": "string",
"default": "[type]",
"description": "Default type to use if a type wasn't able to be detected"
},
"php-docblocker.returnVoid": {
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -142,6 +147,7 @@
}
},
"scripts": {
"clean": "rm -rf ./coverage/* ./.nyc_output ./out",
"verify-pat": "vsce verify-pat",
"vscode:prepublish": "npm run -S esbuild-base -- --minify",
"lint": "tslint -p ./",
Expand All @@ -166,7 +172,7 @@
"esbuild": "^0.12.29",
"markdown-table-ts": "^1.0.3",
"mocha": "^9.1.1",
"nyc": "^15.0.0",
"nyc": "^15.1.0",
"typescript": "^3.9.10",
"vsce": "^1.100.0",
"vscode-test": "^1.6.1"
Expand Down
2 changes: 1 addition & 1 deletion src/block/property.ts
Expand Up @@ -43,7 +43,7 @@ export default class Property extends Block
} else if (params[6]) {
doc.var = TypeUtil.instance.getTypeFromValue(params[6]);
} else {
doc.var = '[type]';
doc.var = TypeUtil.instance.getDefaultType();
}

return doc;
Expand Down
86 changes: 73 additions & 13 deletions src/util/TypeUtil.ts
@@ -1,4 +1,4 @@
import { workspace, TextEditor, Range, Position, TextDocument } from "vscode";
import { TextEditor, Range, Position, TextDocument } from "vscode";
import Config from "./config";

/**
Expand All @@ -23,9 +23,9 @@ export default class TypeUtil {

/**
* Resolve a type string that may contain union types
*
* @param {string} types
* @param {string} head
*
* @param {string} types
* @param {string} head
* @returns {string}
*/
public getResolvedTypeHints(types:string, head:string = null): string
Expand Down Expand Up @@ -54,28 +54,78 @@ export default class TypeUtil {
*/
public getFullyQualifiedType(type:string, head:string):string
{
if (!head) {
return type;
}
if (!Config.instance.get('qualifyClassNames')) {
return type;
}

let useEx = new RegExp("use\\s+([^ ]*?)((?:\\s+as\\s+))?("+type+");", 'gm');
let full = useEx.exec(head);
let useEx = /[\s;]?use\s+(?:(const|function)\s*)?([\s\S]*?)\s*;/gmi;
let exec: RegExpExecArray;
while (exec = useEx.exec(head)) {
let isConstOrFunc = exec[1];
let use = exec[2];

if (full != null && full[3] == type) {
if (full[1].charAt(0) != '\\') {
full[1] = '\\' + full[1];
if (isConstOrFunc) {
continue;
}

if (full[2] != null) {
return full[1];
let clazz = this.getClassesFromUse(use)[type];
if (clazz === undefined) {
continue;
}

return full[1] + type;
if (clazz.charAt(0) != '\\') {
clazz = '\\' + clazz;
}
return clazz;
}

return type;
}

/**
* Returns the classes from the use
*
* @param use
* @returns
*/
public getClassesFromUse(use: string): { [index: string]: string } {
let namespace: string;
let classes: string[];
let hasBracket = use.indexOf('{') !== -1;
if (hasBracket) {
let bracketBegin = use.indexOf('{');
let bracketEnd = (use + '}').indexOf('}');
namespace = use.substring(0, bracketBegin).trim();
classes = use.substring(bracketBegin + 1, bracketEnd).split(',');
} else {
namespace = '';
classes = use.split(',');
}

var results: { [index: string]: string } = {};
for (let index = 0; index < classes.length; index++) {
let alias: string;
let clazz = classes[index].trim();
if (clazz === '') {
continue;
}

clazz = namespace + clazz;

[clazz, alias] = clazz.split(/\s+as\s+/gmi, 2);

if (alias === undefined || alias === '') {
alias = clazz.substring(clazz.lastIndexOf('\\') + 1);
}

results[alias] = clazz;
}
return results;
}

/**
* Returns the user configuration based name for the given type
*
Expand Down Expand Up @@ -136,6 +186,16 @@ export default class TypeUtil {
return 'array';
}

return '[type]';
return this.getDefaultType();
}

/**
* Get the default type
*
* @returns {string}
*/
public getDefaultType(): string
{
return Config.instance.get('defaultType');
}
}
55 changes: 55 additions & 0 deletions test/TypeUtil.test.ts
Expand Up @@ -23,6 +23,14 @@ suite("TypeUtil tests: ", () => {
});
});

test("Test the classes returned from use is correct", () => {
let type = new TypeUtil;
assert.deepEqual(type.getClassesFromUse('ClassA'), { 'ClassA': 'ClassA' });
assert.deepEqual(type.getClassesFromUse('ClassB as B'), { 'B': 'ClassB' });
assert.deepEqual(type.getClassesFromUse('ClassA, namespace\\ClassB as B'), { 'ClassA': 'ClassA', 'B': 'namespace\\ClassB' });
assert.deepEqual(type.getClassesFromUse('namespace\\{ ClassA, ClassB as B }'), { 'ClassA': 'namespace\\ClassA', 'B': 'namespace\\ClassB' });
});

test("Ensure typehint is not mismatched", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
Expand All @@ -47,6 +55,48 @@ suite("TypeUtil tests: ", () => {
assert.equal(type.getFullyQualifiedType('BaseExample', head), '\\App\\Test\\Model\\Example');
});

test("Fully qualify typehint from namespace use with _", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
assert.equal(type.getFullyQualifiedType('ExampleInterface', head), '\\App\\Example\\ExampleInterface');
});

test("Fully qualify typehint from namespace use with bracket", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
assert.equal(type.getFullyQualifiedType('ClassA', head), '\\some\\namespace\\ClassA');
});

test("Fully qualify typehint from namespace use with bracket+alias", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
assert.equal(type.getFullyQualifiedType('ClassB_alias', head), '\\some\\namespace\\ClassB');
});

test("Fully qualify typehint from namespace use with comma", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
assert.equal(type.getFullyQualifiedType('ClassD', head), '\\some\\namespace\\ClassD');
});

test("Fully qualify typehint from namespace use with comma+alias", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
assert.equal(type.getFullyQualifiedType('ClassE_alias', head), '\\some\\namespace\\ClassE');
});

test("Fully qualify typehint from namespace use const", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
assert.equal(type.getFullyQualifiedType('myconst', head), 'myconst');
});

test("Fully qualify typehint from namespace use function", () => {
let type = new TypeUtil;
Helper.setConfig({qualifyClassNames: true});
assert.equal(type.getFullyQualifiedType('myfunction', head), 'myfunction');
});

test("With default settings the integer type formatted is integer", () => {
let type = new TypeUtil;
assert.equal(type.getFormattedTypeByName('int'), 'integer');
Expand Down Expand Up @@ -74,6 +124,11 @@ suite("TypeUtil tests: ", () => {
assert.equal(type.getFormattedTypeByName('helloWorld'), 'helloWorld');
});

test("Undefined type uses config", () => {
let type = new TypeUtil;
Helper.setConfig({defaultType: 'mixed'});
assert.equal(type.getDefaultType(), 'mixed');
});
});

suite("TypeUtil issue test: ", () => {
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/namespace.php
Expand Up @@ -12,6 +12,19 @@
use Psr\Log\LoggerInterface;
use Psr\Log\InvalidArgumentException;

use Example_ExampleInterface;
use App\Example\ExampleInterface;

use some\namespace\{
ClassA,
ClassB as ClassB_alias,
};
use some\namespace\ClassD,
some\namespace\ClassE as ClassE_alias;

use const some\namespace\myconst;
use function some\namespace\myfunction;

/**
* Example class here
*/
Expand Down
2 changes: 1 addition & 1 deletion test/index.ts
Expand Up @@ -53,6 +53,6 @@ export async function run(): Promise<void> {
});
} finally {
nyc.writeCoverageFile();
nyc.report();
await nyc.report();
}
}

0 comments on commit 4356c9c

Please sign in to comment.