fix(lint): enforce that module-private members have @internal.
This is needed to prevent leaking internal APIs to users via our published .d.ts typings. Fixes #4645 Closes #4989
This commit is contained in:
parent
44188b9072
commit
098201d0b8
|
@ -294,6 +294,7 @@ gulp.task('lint', ['build.tools'], function() {
|
|||
// https://github.com/palantir/tslint#supported-rules
|
||||
var tslintConfig = {
|
||||
"rules": {
|
||||
"requireInternalWithUnderscore": true,
|
||||
"requireParameterType": true,
|
||||
"requireReturnType": true,
|
||||
"semicolon": true,
|
||||
|
|
|
@ -265,6 +265,7 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
|||
return this._addRecord(RecordType.Chain, "chain", null, args, null, 0);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_visitAll(asts: any[]) {
|
||||
var res = ListWrapper.createFixedSize(asts.length);
|
||||
for (var i = 0; i < asts.length; ++i) {
|
||||
|
@ -273,6 +274,7 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
|||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addRecord(type, name, funcOrValue, args, fixedArgs, context) {
|
||||
var selfIndex = this._records.length + 1;
|
||||
if (context instanceof DirectiveIndex) {
|
||||
|
|
|
@ -52,8 +52,8 @@ export abstract class LifeCycle {
|
|||
|
||||
@Injectable()
|
||||
export class LifeCycle_ extends LifeCycle {
|
||||
/** @internal */
|
||||
static _tickScope: WtfScopeFn = wtfCreateScope('LifeCycle#tick()');
|
||||
|
||||
/** @internal */
|
||||
_changeDetectors: ChangeDetector[];
|
||||
/** @internal */
|
||||
|
|
|
@ -119,11 +119,13 @@ export class DirectiveDependency extends Dependency {
|
|||
DirectiveDependency._attributeName(d.properties), DirectiveDependency._query(d.properties));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static _attributeName(properties): string {
|
||||
var p = <AttributeMetadata>ListWrapper.find(properties, (p) => p instanceof AttributeMetadata);
|
||||
return isPresent(p) ? p.attributeName : null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static _query(properties): QueryMetadata {
|
||||
return <QueryMetadata>ListWrapper.find(properties, (p) => p instanceof QueryMetadata);
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ var defaultLocale: string = 'en-US';
|
|||
@Pipe({name: 'date'})
|
||||
@Injectable()
|
||||
export class DatePipe implements PipeTransform {
|
||||
/** @internal */
|
||||
static _ALIASES: {[key: string]: String} = {
|
||||
'medium': 'yMMMdjms',
|
||||
'short': 'yMdjm',
|
||||
|
|
|
@ -23,6 +23,7 @@ var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
|
|||
@CONST()
|
||||
@Injectable()
|
||||
export class NumberPipe {
|
||||
/** @internal */
|
||||
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
|
||||
currencyAsSymbol: boolean = false): string {
|
||||
if (isBlank(value)) return null;
|
||||
|
|
|
@ -99,6 +99,7 @@ export class KeyEventsPlugin extends EventManagerPlugin {
|
|||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static _normalizeKey(keyName: string): string {
|
||||
// TODO: switch to a StringMap if the mapping grows too much
|
||||
switch (keyName) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
import {global} from 'angular2/src/core/facade/lang';
|
||||
|
||||
class PublicTestability {
|
||||
/** @internal */
|
||||
_testability: Testability;
|
||||
|
||||
constructor(testability: Testability) { this._testability = testability; }
|
||||
|
|
|
@ -142,6 +142,7 @@ class MessageData {
|
|||
|
||||
/**
|
||||
* Returns the value from the StringMap if present. Otherwise returns null
|
||||
* @internal
|
||||
*/
|
||||
_getValueIfPresent(data: {[key: string]: any}, key: string) {
|
||||
if (StringMapWrapper.contains(data, key)) {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import {RuleFailure} from 'tslint/lib/lint';
|
||||
import {AbstractRule} from 'tslint/lib/rules';
|
||||
import {RuleWalker} from 'tslint/lib/language/walker';
|
||||
import * as ts from 'tslint/node_modules/typescript';
|
||||
|
||||
export class Rule extends AbstractRule {
|
||||
public apply(sourceFile: ts.SourceFile): RuleFailure[] {
|
||||
const typedefWalker = new TypedefWalker(sourceFile, this.getOptions());
|
||||
return this.applyWithWalker(typedefWalker);
|
||||
}
|
||||
}
|
||||
|
||||
class TypedefWalker extends RuleWalker {
|
||||
protected visitPropertyDeclaration(node: ts.PropertyDeclaration): void {
|
||||
this.assertInternalAnnotationPresent(node);
|
||||
super.visitPropertyDeclaration(node);
|
||||
}
|
||||
|
||||
public visitMethodDeclaration(node: ts.MethodDeclaration): void {
|
||||
this.assertInternalAnnotationPresent(node);
|
||||
super.visitMethodDeclaration(node);
|
||||
}
|
||||
|
||||
private hasInternalAnnotation(range: ts.CommentRange): boolean {
|
||||
let text = this.getSourceFile().text;
|
||||
let comment = text.substring(range.pos, range.end);
|
||||
return comment.indexOf("@internal") >= 0;
|
||||
}
|
||||
|
||||
private assertInternalAnnotationPresent(node: ts.Declaration) {
|
||||
if (node.name.getText().charAt(0) !== '_') return;
|
||||
if (node.modifiers && node.modifiers.flags & ts.NodeFlags.Private) return;
|
||||
|
||||
const ranges = ts.getLeadingCommentRanges(this.getSourceFile().text, node.pos);
|
||||
if (ranges) {
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
if (this.hasInternalAnnotation(ranges[i])) return;
|
||||
}
|
||||
}
|
||||
this.addFailure(this.createFailure(
|
||||
node.getStart(), node.getWidth(),
|
||||
`module-private member ${node.name.getText()} must be annotated @internal`));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue