Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"typescript": "2.7.2"
},
"devDependencies": {
"@types/mocha": "2.2.48",
"@types/mocha": "^5.0.0",
"grunt": "^1.0.2",
"grunt-cli": "^1.2.0",
"grunt-contrib-clean": "^1.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { Application } from './lib/application';
export { CliApplication } from './lib/cli';

export { EventDispatcher, Event } from './lib/utils/events';
export { createMinimatch } from './lib/utils/paths';
export { resetReflectionID } from './lib/models/reflections/abstract';
export { normalizePath } from './lib/utils/fs';
export * from './lib/models/reflections';
Expand Down
7 changes: 5 additions & 2 deletions src/lib/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import * as Path from 'path';
import * as FS from 'fs';
import * as typescript from 'typescript';
import { Minimatch, IMinimatch } from 'minimatch';

import { Converter } from './converter/index';
import { Renderer } from './output/renderer';
import { Serializer } from './serialization';
import { ProjectReflection } from './models/index';
import { Logger, ConsoleLogger, CallbackLogger, PluginHost, writeFile } from './utils/index';
import { createMinimatch } from './utils/paths';

import { AbstractComponent, ChildableComponent, Component, Option } from './utils/component';
import { Options, OptionsReadMode, OptionsReadResult } from './utils/options/index';
Expand Down Expand Up @@ -248,7 +248,10 @@ export class Application extends ChildableComponent<Application, AbstractCompone
*/
public expandInputFiles(inputFiles?: string[]): string[] {
let files: string[] = [];
const exclude: Array<IMinimatch> = this.exclude ? this.exclude.map(pattern => new Minimatch(pattern)) : [];

const exclude = this.exclude
? createMinimatch(this.exclude)
: [];

function isExcluded(fileName: string): boolean {
return exclude.some(mm => mm.match(fileName));
Expand Down
13 changes: 7 additions & 6 deletions src/lib/converter/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as ts from 'typescript';
import { Minimatch, IMinimatch } from 'minimatch';
import { IMinimatch } from 'minimatch';

import { Logger } from '../utils/loggers';
import { createMinimatch } from '../utils/paths';
import { Reflection, ProjectReflection, ContainerReflection, Type } from '../models/index';

import { createTypeParameter } from './factories/type-parameter';
import { Converter } from './converter';

Expand Down Expand Up @@ -93,7 +95,7 @@ export class Context {
/**
* The pattern that should be used to flag external source files.
*/
private externalPattern: IMinimatch;
private externalPattern: Array<IMinimatch>;

/**
* Create a new Context instance.
Expand All @@ -114,7 +116,7 @@ export class Context {
this.scope = project;

if (converter.externalPattern) {
this.externalPattern = new Minimatch(converter.externalPattern);
this.externalPattern = createMinimatch(converter.externalPattern);
}
}

Expand Down Expand Up @@ -216,10 +218,9 @@ export class Context {
* @param callback The callback that should be executed.
*/
withSourceFile(node: ts.SourceFile, callback: Function) {
const externalPattern = this.externalPattern;
let isExternal = this.fileNames.indexOf(node.fileName) === -1;
if (externalPattern) {
isExternal = isExternal || externalPattern.match(node.fileName);
if (!isExternal && this.externalPattern) {
isExternal = this.externalPattern.some(mm => mm.match(node.fileName));
}

if (isExternal && this.converter.excludeExternals) {
Expand Down
5 changes: 3 additions & 2 deletions src/lib/converter/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ export class Converter extends ChildableComponent<Application, ConverterComponen

@Option({
name: 'externalPattern',
help: 'Define a pattern for files that should be considered being external.'
help: 'Define patterns for files that should be considered being external.',
type: ParameterType.Array
})
externalPattern: string;
externalPattern: Array<string>;

@Option({
name: 'includeDeclarations',
Expand Down
40 changes: 23 additions & 17 deletions src/lib/utils/options/readers/tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,32 @@ export class TSConfigReader extends OptionsComponent {
return;
}

let file: string;

if (TSConfigReader.OPTIONS_KEY in event.data) {
this.load(event, Path.resolve(event.data[TSConfigReader.OPTIONS_KEY]));
const tsconfig = event.data[TSConfigReader.OPTIONS_KEY];

if (/tsconfig\.json$/.test(tsconfig)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this line is responsible for making typedoc 0.14 a breaking change for my projects.

The path that I use for my config file is something like node_modules/my-config-project/config/typedoc.config.json

This worked fine in 0.13 but breaks in 0.14.

I now need to update the path on 60+ projects..

file = Path.resolve(tsconfig);
} else {
file = ts.findConfigFile(tsconfig, ts.sys.fileExists);
}

if (!file || !FS.existsSync(file)) {
event.addError('The tsconfig file %s does not exist.', file);
return;
}
} else if (TSConfigReader.PROJECT_KEY in event.data) {
// The `project` option may be a directory or file, so use TS to find it
let file: string = ts.findConfigFile(event.data[TSConfigReader.PROJECT_KEY], ts.sys.fileExists);
// If file is undefined, we found no file to load.
if (file) {
this.load(event, file);
}
file = ts.findConfigFile(event.data[TSConfigReader.PROJECT_KEY], ts.sys.fileExists);
} else if (this.application.isCLI) {
let file: string = ts.findConfigFile('.', ts.sys.fileExists);
// If file is undefined, we found no file to load.
if (file) {
this.load(event, file);
}
// No file or directory has been specified so find the file in the root of the project
file = ts.findConfigFile('.', ts.sys.fileExists);
}

// If file is undefined, we found no file to load.
if (file) {
this.load(event, file);
}
}

Expand All @@ -65,14 +76,9 @@ export class TSConfigReader extends OptionsComponent {
* @param fileName The absolute path and file name of the tsconfig file.
*/
load(event: DiscoverEvent, fileName: string) {
if (!FS.existsSync(fileName)) {
event.addError('The tsconfig file %s does not exist.', fileName);
return;
}

const { config } = ts.readConfigFile(fileName, ts.sys.readFile);
if (config === undefined) {
event.addError('The tsconfig file %s does not contain valid JSON.', fileName);
event.addError('No valid tsconfig file found for %s.', fileName);
return;
}
if (!_.isPlainObject(config)) {
Expand Down
51 changes: 39 additions & 12 deletions src/lib/utils/options/readers/typedoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,57 @@ export class TypedocReader extends OptionsComponent {
return;
}

let file: string;

if (TypedocReader.OPTIONS_KEY in event.data) {
this.load(event, Path.resolve(event.data[TypedocReader.OPTIONS_KEY]));
} else if (this.application.isCLI) {
const file = Path.resolve('typedoc.js');
if (FS.existsSync(file)) {
this.load(event, file);
let opts = event.data[TypedocReader.OPTIONS_KEY];

if (opts && opts[0] === '.') {
opts = Path.resolve(opts);
}

file = this.findTypedocFile(opts);

if (!FS.existsSync(file)) {
event.addError('The options file could not be found with the given path %s.', opts);
return;
}
} else if (this.application.isCLI) {
file = this.findTypedocFile();
}

file && this.load(event, file);
}

/**
* Search for the typedoc.js or typedoc.json file from the given path
*
* @param path Path to the typedoc.(js|json) file. If path is a directory
* typedoc file will be attempted to be found at the root of this path
* @return the typedoc.(js|json) file path or undefined
*/
findTypedocFile(path: string = process.cwd()): string | undefined {
if (/typedoc\.js(on)?$/.test(path)) {
return path;
}

let file = Path.join(path, 'typedoc.js');
if (FS.existsSync(file)) {
return file;
}

file += 'on'; // look for JSON file
return FS.existsSync(file) ? file : undefined;
}

/**
* Load the specified option file.
*
* @param event The event object from the DISCOVER event.
* @param optionFile The absolute path and file name of the option file.
* @param ignoreUnknownArgs Should unknown arguments be ignored? If so the parser
* will simply skip all unknown arguments.
* @returns TRUE on success, otherwise FALSE.
*/
load(event: DiscoverEvent, optionFile: string) {
if (!FS.existsSync(optionFile)) {
event.addError('The option file %s does not exist.', optionFile);
return;
}

let data = require(optionFile);
if (typeof data === 'function') {
data = data(this.application);
Expand Down
28 changes: 28 additions & 0 deletions src/lib/utils/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as Path from 'path';
import { isArray } from 'util';
import { Minimatch, IMinimatch } from 'minimatch';

function convertToMinimatch(pattern: string): IMinimatch {
// Ensure uniform absolute path cross OS
// (POSIX would resolve c:/path to /path/to/repo/c:/path without this check)
if (Path.isAbsolute(pattern) && Path.sep === '/') {
pattern = pattern.replace(/^\w:/, '');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the drive letter is removed, does this break access to other drives (e.g. d:/path) or was that already broken?

Copy link
Copy Markdown
Contributor Author

@Tokimon Tokimon Apr 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it shouldn't.
At least to my knowledge. As far as I understand it the drive letter notation is really a windows only thing and on other systems rather use names for different drives like /externaldrive/some/path or /drive2/some/path.
Therefore any drive notation is stripped on these systems, but mostly as a precaution, as you never know how people write their paths.
However drive notated paths on Windows systems are kept intact (thanks to the Path.sep === '/' check which ensures this is only done on non Windows machines).

You could argue that the check is redundant, as people probably wouldn't use the drive notation anyway. But I am just so used to try and foresee stupid errors that I added it.

Let me know if you think we keep it or not.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks for explaining. We can leave it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an update on the UNIX pathing system. I asked around and for UNIX systems (Linxus + MAC) you change drive by typing something like /volumes/drive. So absolute paths always start with a '/' on those systems. So it should be good like this.

}

if (pattern[0] !== '*') {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be checking for **? If the path is not resolved for */test.js then I think it won't work with /project/base/*/test.js which is what I would expect

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm... yeah you might be right... I will correct it.

// pattern path is resolved even if it is an absolute path,
// to ensure correct format for the current OS
pattern = Path.resolve(pattern);
}

// Unify the path slashes before creating the minimatch, for more relyable matching
return new Minimatch(pattern.replace(/[\\]/g, '/'), { dot: true });
}

export function createMinimatch(pattern: string[]): IMinimatch[];
export function createMinimatch(pattern: string): IMinimatch;
export function createMinimatch(pattern: string | string[]): IMinimatch | IMinimatch[] {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since all usages are IMinimatch[] and to avoid overloaded functions, please remove this function and inline x.map(convertToMinimatch) where createMinimatch was being used.

Copy link
Copy Markdown
Contributor Author

@Tokimon Tokimon Apr 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok... if you prefer, I can remove the overloads, but as the function is used two places already (which is why I created it in the first place), so I prefer to keep the function it self to avoid having duplicate the code in the code base.
And it is easier to unit test the method used if it is isolated like this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, that sounds good to me

return isArray(pattern)
? pattern.map(convertToMinimatch)
: convertToMinimatch(pattern);
}
2 changes: 2 additions & 0 deletions src/test/renderer/specs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ <h4 id="typescript-compiler">TypeScript compiler</h4>
Specify module code generation: &quot;commonjs&quot;, &quot;amd&quot;, &quot;system&quot; or &quot;umd&quot;.</li>
<li><code>--target &lt;ES3, ES5, or ES6&gt;</code><br>
Specify ECMAScript target version: &quot;ES3&quot; (default), &quot;ES5&quot; or &quot;ES6&quot;</li>
<li><code>--tsconfig &lt;path/to/tsconfig.json&gt;</code><br>
Specify a typescript config file that should be loaded. If not specified TypeDoc will look for &#39;tsconfig.json&#39; in the current directory.</li>
</ul>
<h4 id="theming">Theming</h4>
<ul>
Expand Down
66 changes: 66 additions & 0 deletions src/test/utils.paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as Path from 'path';
import { Minimatch } from 'minimatch';

import isEqual = require('lodash/isEqual');
import Assert = require('assert');

import { createMinimatch } from '..';

// Used to ensure uniform path cross OS
const absolutePath = (path: string) => Path.resolve(path.replace(/^\w:/, '')).replace(/[\\]/g, '/');

describe('Paths', () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing tests!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure thing

describe('createMinimatch', () => {
it('Converts a path to a Minimatch expression', () => {
const mm = createMinimatch('/some/path/**');
Assert(mm instanceof Minimatch, 'Path not coverted to Minimatch');
});

it('Converts an array of paths to an array of Minimatch expressions', () => {
const mms = createMinimatch(['/some/path/**', '**/another/path/**', './relative/**/path']);
Assert(Array.isArray(mms), 'Didn\'t return an array');

const allAreMm = mms.every((mm) => mm instanceof Minimatch);
Assert(allAreMm, 'Not all paths are coverted to Minimatch');
});

it('Minimatch can match absolute paths expressions', () => {
const paths = ['/unix/absolute/**/path', '\\windows\\alternative\\absolute\\path', 'C:\\Windows\\absolute\\*\\path', '**/arbitrary/path/**'];
const mms = createMinimatch(paths);
const patterns = mms.map(({ pattern }) => pattern);
const comparePaths = [
absolutePath('/unix/absolute/**/path'),
absolutePath('/windows/alternative/absolute/path'),
absolutePath('/Windows/absolute/*/path'),
'**/arbitrary/path/**'
];

Assert(isEqual(patterns, comparePaths), `Patterns have been altered:\nMMS: ${patterns}\nPaths: ${comparePaths}`);

Assert(mms[0].match(absolutePath('/unix/absolute/some/sub/dir/path')), 'Din\'t match unix path');
Assert(mms[1].match(absolutePath('/windows/alternative/absolute/path')), 'Din\'t match windows alternative path');
Assert(mms[2].match(absolutePath('/Windows/absolute/test/path')), 'Din\'t match windows path');
Assert(mms[3].match(absolutePath('/some/deep/arbitrary/path/leading/nowhere')), 'Din\'t match arbitrary path');
});

it('Minimatch can match relative to the project root', () => {
const paths = ['./relative/**/path', '../parent/*/path', 'no/dot/relative/**/path/*', '.dot/relative/**/path/*'];
const absPaths = paths.map((path) => absolutePath(path));
const mms = createMinimatch(paths);
const patterns = mms.map(({ pattern }) => pattern);

Assert(isEqual(patterns, absPaths), `Project root have not been added to paths:\nMMS: ${patterns}\nPaths: ${absPaths}`);

Assert(mms[0].match(Path.resolve('relative/some/sub/dir/path')), 'Din\'t match relative path');
Assert(mms[1].match(Path.resolve('../parent/dir/path')), 'Din\'t match parent path');
Assert(mms[2].match(Path.resolve('no/dot/relative/some/sub/dir/path/test')), 'Din\'t match no dot path');
Assert(mms[3].match(Path.resolve('.dot/relative/some/sub/dir/path/test')), 'Din\'t match dot path');
});

it('Minimatch matches dot files', () => {
const mm = createMinimatch('/some/path/**');
Assert(mm.match(absolutePath('/some/path/.dot/dir')), 'Didn\'t match .dot path');
Assert(mm.match(absolutePath('/some/path/normal/dir')), 'Didn\'t match normal path');
});
});
});
2 changes: 1 addition & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"curly": true,
"eofline": true,
"forin": false,
"indent": [ true, "spaces" ],
"indent": [ true, "spaces", 4 ],
"interface-name": [ true, "never-prefix" ],
"jsdoc-format": true,
"label-position": true,
Expand Down