11import * as ts from "typescript" ;
2+ import * as path from "path" ;
23import { Decorator , DecoratorKind } from "./Decorator" ;
34import * as tstl from "./LuaAST" ;
5+ import * as TSTLErrors from "./TSTLErrors" ;
6+ import { EmitResolver } from "./LuaTransformer" ;
47
58export enum ContextType {
69 None ,
@@ -53,6 +56,51 @@ export function getExtendedType(node: ts.ClassLikeDeclarationBase, checker: ts.T
5356 return extendedTypeNode && checker . getTypeAtLocation ( extendedTypeNode ) ;
5457}
5558
59+ export function isAssignmentPattern ( node : ts . Node ) : node is ts . AssignmentPattern {
60+ return ts . isObjectLiteralExpression ( node ) || ts . isArrayLiteralExpression ( node ) ;
61+ }
62+
63+ export function getExportable ( exportSpecifiers : ts . NamedExports , resolver : EmitResolver ) : ts . ExportSpecifier [ ] {
64+ return exportSpecifiers . elements . filter ( exportSpecifier => resolver . isValueAliasDeclaration ( exportSpecifier ) ) ;
65+ }
66+
67+ export function isDefaultExportSpecifier ( node : ts . ExportSpecifier ) : boolean {
68+ return (
69+ ( node . name !== undefined && node . name . originalKeywordKind === ts . SyntaxKind . DefaultKeyword ) ||
70+ ( node . propertyName !== undefined && node . propertyName . originalKeywordKind === ts . SyntaxKind . DefaultKeyword )
71+ ) ;
72+ }
73+
74+ export function hasDefaultExportModifier ( modifiers ?: ts . NodeArray < ts . Modifier > ) : boolean {
75+ return modifiers ? modifiers . some ( modifier => modifier . kind === ts . SyntaxKind . DefaultKeyword ) : false ;
76+ }
77+
78+ export function shouldResolveModulePath ( moduleSpecifier : ts . Expression , checker : ts . TypeChecker ) : boolean {
79+ const moduleOwnerSymbol = checker . getSymbolAtLocation ( moduleSpecifier ) ;
80+ if ( moduleOwnerSymbol ) {
81+ const decorators = new Map < DecoratorKind , Decorator > ( ) ;
82+ collectCustomDecorators ( moduleOwnerSymbol , checker , decorators ) ;
83+ if ( decorators . has ( DecoratorKind . NoResolution ) ) {
84+ return false ;
85+ }
86+ }
87+ return true ;
88+ }
89+
90+ export function shouldBeImported (
91+ importNode : ts . ImportClause | ts . ImportSpecifier ,
92+ checker : ts . TypeChecker ,
93+ resolver : EmitResolver
94+ ) : boolean {
95+ const decorators = getCustomDecorators ( checker . getTypeAtLocation ( importNode ) , checker ) ;
96+
97+ return (
98+ resolver . isReferencedAliasDeclaration ( importNode ) &&
99+ ! decorators . has ( DecoratorKind . Extension ) &&
100+ ! decorators . has ( DecoratorKind . MetaExtension )
101+ ) ;
102+ }
103+
56104export function isFileModule ( sourceFile : ts . SourceFile ) : boolean {
57105 return sourceFile . statements . some ( isStatementExported ) ;
58106}
@@ -132,34 +180,45 @@ export function isStaticNode(node: ts.Node): boolean {
132180 return node . modifiers !== undefined && node . modifiers . some ( m => m . kind === ts . SyntaxKind . StaticKeyword ) ;
133181}
134182
135- export function isStringType ( type : ts . Type , checker : ts . TypeChecker , program : ts . Program ) : boolean {
183+ export function isTypeWithFlags (
184+ type : ts . Type ,
185+ flags : ts . TypeFlags ,
186+ checker : ts . TypeChecker ,
187+ program : ts . Program
188+ ) : boolean {
136189 if ( type . symbol ) {
137190 const baseConstraint = checker . getBaseConstraintOfType ( type ) ;
138191 if ( baseConstraint && baseConstraint !== type ) {
139- return isStringType ( baseConstraint , checker , program ) ;
192+ return isTypeWithFlags ( baseConstraint , flags , checker , program ) ;
140193 }
141194 }
142195
143196 if ( type . isUnion ( ) ) {
144- return type . types . every ( t => isStringType ( t , checker , program ) ) ;
197+ return type . types . every ( t => isTypeWithFlags ( t , flags , checker , program ) ) ;
145198 }
146199
147200 if ( type . isIntersection ( ) ) {
148- return type . types . some ( t => isStringType ( t , checker , program ) ) ;
201+ return type . types . some ( t => isTypeWithFlags ( t , flags , checker , program ) ) ;
149202 }
150203
151- return (
152- ( type . flags & ts . TypeFlags . String ) !== 0 ||
153- ( type . flags & ts . TypeFlags . StringLike ) !== 0 ||
154- ( type . flags & ts . TypeFlags . StringLiteral ) !== 0
204+ return ( type . flags & flags ) !== 0 ;
205+ }
206+
207+ export function isStringType ( type : ts . Type , checker : ts . TypeChecker , program : ts . Program ) : boolean {
208+ return isTypeWithFlags (
209+ type ,
210+ ts . TypeFlags . String | ts . TypeFlags . StringLike | ts . TypeFlags . StringLiteral ,
211+ checker ,
212+ program
155213 ) ;
156214}
157215
158- export function isNumberType ( type : ts . Type ) : boolean {
159- return (
160- ( type . flags & ts . TypeFlags . Number ) !== 0 ||
161- ( type . flags & ts . TypeFlags . NumberLike ) !== 0 ||
162- ( type . flags & ts . TypeFlags . NumberLiteral ) !== 0
216+ export function isNumberType ( type : ts . Type , checker : ts . TypeChecker , program : ts . Program ) : boolean {
217+ return isTypeWithFlags (
218+ type ,
219+ ts . TypeFlags . Number | ts . TypeFlags . NumberLike | ts . TypeFlags . NumberLiteral ,
220+ checker ,
221+ program
163222 ) ;
164223}
165224
@@ -337,19 +396,25 @@ export function getCustomDecorators(type: ts.Type, checker: ts.TypeChecker): Map
337396 return decMap ;
338397}
339398
399+ export function getCustomNodeDirectives ( node : ts . Node ) : Map < DecoratorKind , Decorator > {
400+ const directivesMap = new Map < DecoratorKind , Decorator > ( ) ;
401+
402+ ts . getJSDocTags ( node ) . forEach ( tag => {
403+ const tagName = tag . tagName . escapedText as string ;
404+ if ( Decorator . isValid ( tagName ) ) {
405+ const dec = new Decorator ( tagName , tag . comment ? tag . comment . split ( " " ) : [ ] ) ;
406+ directivesMap . set ( dec . kind , dec ) ;
407+ }
408+ } ) ;
409+
410+ return directivesMap ;
411+ }
412+
340413export function getCustomFileDirectives ( file : ts . SourceFile ) : Map < DecoratorKind , Decorator > {
341- const decMap = new Map < DecoratorKind , Decorator > ( ) ;
342414 if ( file . statements . length > 0 ) {
343- const tags = ts . getJSDocTags ( file . statements [ 0 ] ) ;
344- for ( const tag of tags ) {
345- const tagName = tag . tagName . escapedText as string ;
346- if ( Decorator . isValid ( tagName ) ) {
347- const dec = new Decorator ( tagName , tag . comment ? tag . comment . split ( " " ) : [ ] ) ;
348- decMap . set ( dec . kind , dec ) ;
349- }
350- }
415+ return getCustomNodeDirectives ( file . statements [ 0 ] ) ;
351416 }
352- return decMap ;
417+ return new Map ( ) ;
353418}
354419
355420export function getCustomSignatureDirectives (
@@ -596,8 +661,7 @@ export function hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeC
596661 if ( ts . isSourceFile ( scopeDeclaration ) ) {
597662 return getCustomFileDirectives ( scopeDeclaration ) . has ( DecoratorKind . NoSelfInFile ) ;
598663 }
599- const scopeType = checker . getTypeAtLocation ( scopeDeclaration ) ;
600- if ( scopeType && getCustomDecorators ( scopeType , checker ) . has ( DecoratorKind . NoSelf ) ) {
664+ if ( getCustomNodeDirectives ( scopeDeclaration ) . has ( DecoratorKind . NoSelf ) ) {
601665 return true ;
602666 }
603667 return hasNoSelfAncestor ( scopeDeclaration , checker ) ;
@@ -634,8 +698,7 @@ export function getDeclarationContextType(
634698 return ContextType . NonVoid ;
635699 }
636700
637- const scopeType = checker . getTypeAtLocation ( scopeDeclaration ) ;
638- if ( scopeType && getCustomDecorators ( scopeType , checker ) . has ( DecoratorKind . NoSelf ) ) {
701+ if ( getCustomNodeDirectives ( scopeDeclaration ) . has ( DecoratorKind . NoSelf ) ) {
639702 return ContextType . Void ;
640703 }
641704 return ContextType . NonVoid ;
@@ -812,8 +875,14 @@ export function isWithinLiteralAssignmentStatement(node: ts.Node): boolean {
812875 if ( ! node . parent ) {
813876 return false ;
814877 }
815- if ( ts . isArrayLiteralExpression ( node . parent ) || ts . isObjectLiteralExpression ( node . parent ) ) {
878+ if (
879+ ts . isArrayLiteralExpression ( node . parent ) ||
880+ ts . isArrayBindingPattern ( node . parent ) ||
881+ ts . isObjectLiteralExpression ( node . parent )
882+ ) {
816883 return isWithinLiteralAssignmentStatement ( node . parent ) ;
884+ } else if ( isInDestructingAssignment ( node ) ) {
885+ return true ;
817886 } else if ( ts . isBinaryExpression ( node . parent ) && node . parent . operatorToken . kind === ts . SyntaxKind . EqualsToken ) {
818887 return true ;
819888 } else {
@@ -839,27 +908,51 @@ export function moduleHasEmittedBody(
839908 return false ;
840909}
841910
842- export function isArrayLengthAssignment (
843- expression : ts . BinaryExpression ,
911+ export function isValidFlattenableDestructuringAssignmentLeftHandSide (
912+ node : ts . DestructuringAssignment ,
844913 checker : ts . TypeChecker ,
845914 program : ts . Program
846- ) : expression is ts . BinaryExpression & { left : ts . PropertyAccessExpression | ts . ElementAccessExpression } {
847- if ( expression . operatorToken . kind !== ts . SyntaxKind . EqualsToken ) {
848- return false ;
915+ ) : boolean {
916+ if ( ts . isArrayLiteralExpression ( node . left ) ) {
917+ if ( node . left . elements . length > 0 ) {
918+ return ! node . left . elements . some ( element => {
919+ switch ( element . kind ) {
920+ case ts . SyntaxKind . Identifier :
921+ case ts . SyntaxKind . PropertyAccessExpression :
922+ if ( isArrayLength ( element , checker , program ) ) {
923+ return true ;
924+ }
925+ case ts . SyntaxKind . ElementAccessExpression :
926+ // Can be on the left hand side of a Lua assignment statement
927+ return false ;
928+ default :
929+ // Cannot be
930+ return true ;
931+ }
932+ } ) ;
933+ }
849934 }
850935
851- if ( ! ts . isPropertyAccessExpression ( expression . left ) && ! ts . isElementAccessExpression ( expression . left ) ) {
936+ return false ;
937+ }
938+
939+ export function isArrayLength (
940+ expression : ts . Expression ,
941+ checker : ts . TypeChecker ,
942+ program : ts . Program
943+ ) : expression is ts . PropertyAccessExpression | ts . ElementAccessExpression {
944+ if ( ! ts . isPropertyAccessExpression ( expression ) && ! ts . isElementAccessExpression ( expression ) ) {
852945 return false ;
853946 }
854947
855- const type = checker . getTypeAtLocation ( expression . left . expression ) ;
948+ const type = checker . getTypeAtLocation ( expression . expression ) ;
856949 if ( ! isArrayType ( type , checker , program ) ) {
857950 return false ;
858951 }
859952
860- const name = ts . isPropertyAccessExpression ( expression . left )
861- ? ( expression . left . name . escapedText as string )
862- : ts . isStringLiteral ( expression . left . argumentExpression ) && expression . left . argumentExpression . text ;
953+ const name = ts . isPropertyAccessExpression ( expression )
954+ ? ( expression . name . escapedText as string )
955+ : ts . isStringLiteral ( expression . argumentExpression ) && expression . argumentExpression . text ;
863956
864957 return name === "length" ;
865958}
@@ -899,3 +992,55 @@ export function isSimpleExpression(expression: tstl.Expression): boolean {
899992 }
900993 return true ;
901994}
995+
996+ export function getAbsoluteImportPath (
997+ relativePath : string ,
998+ directoryPath : string ,
999+ options : ts . CompilerOptions
1000+ ) : string {
1001+ if ( relativePath . charAt ( 0 ) !== "." && options . baseUrl ) {
1002+ return path . resolve ( options . baseUrl , relativePath ) ;
1003+ }
1004+
1005+ return path . resolve ( directoryPath , relativePath ) ;
1006+ }
1007+
1008+ export function getImportPath (
1009+ fileName : string ,
1010+ relativePath : string ,
1011+ node : ts . Node ,
1012+ options : ts . CompilerOptions
1013+ ) : string {
1014+ const rootDir = options . rootDir ? path . resolve ( options . rootDir ) : path . resolve ( "." ) ;
1015+
1016+ const absoluteImportPath = path . format (
1017+ path . parse ( getAbsoluteImportPath ( relativePath , path . dirname ( fileName ) , options ) )
1018+ ) ;
1019+ const absoluteRootDirPath = path . format ( path . parse ( rootDir ) ) ;
1020+ if ( absoluteImportPath . includes ( absoluteRootDirPath ) ) {
1021+ return formatPathToLuaPath ( absoluteImportPath . replace ( absoluteRootDirPath , "" ) . slice ( 1 ) ) ;
1022+ } else {
1023+ throw TSTLErrors . UnresolvableRequirePath (
1024+ node ,
1025+ `Cannot create require path. Module does not exist within --rootDir` ,
1026+ relativePath
1027+ ) ;
1028+ }
1029+ }
1030+
1031+ export function getExportPath ( fileName : string , options : ts . CompilerOptions ) : string {
1032+ const rootDir = options . rootDir ? path . resolve ( options . rootDir ) : path . resolve ( "." ) ;
1033+
1034+ const absolutePath = path . resolve ( fileName . replace ( / .t s $ / , "" ) ) ;
1035+ const absoluteRootDirPath = path . format ( path . parse ( rootDir ) ) ;
1036+ return formatPathToLuaPath ( absolutePath . replace ( absoluteRootDirPath , "" ) . slice ( 1 ) ) ;
1037+ }
1038+
1039+ export function formatPathToLuaPath ( filePath : string ) : string {
1040+ filePath = filePath . replace ( / \. j s o n $ / , "" ) ;
1041+ if ( process . platform === "win32" ) {
1042+ // Windows can use backslashes
1043+ filePath = filePath . replace ( / \. \\ / g, "" ) . replace ( / \\ / g, "." ) ;
1044+ }
1045+ return filePath . replace ( / \. \/ / g, "" ) . replace ( / \/ / g, "." ) ;
1046+ }
0 commit comments