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:
Alex Eagle 2015-10-28 15:04:55 -07:00 committed by Alex Eagle
parent 44188b9072
commit 098201d0b8
10 changed files with 55 additions and 1 deletions

View File

@ -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,

View File

@ -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) {

View File

@ -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 */

View File

@ -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);
}

View File

@ -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',

View File

@ -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;

View File

@ -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) {

View File

@ -7,6 +7,7 @@ import {
import {global} from 'angular2/src/core/facade/lang';
class PublicTestability {
/** @internal */
_testability: Testability;
constructor(testability: Testability) { this._testability = testability; }

View File

@ -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)) {

View File

@ -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`));
}
}