Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
30 changes: 15 additions & 15 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -26592,21 +26592,6 @@ func (c *Checker) isAssignmentToReadonlyEntity(expr *ast.Node, symbol *ast.Symbo
// no assignment means it doesn't matter whether the entity is readonly
return false
}
if ast.IsAccessExpression(expr) {
node := ast.SkipParentheses(expr.Expression())
if ast.IsIdentifier(node) {
expressionSymbol := c.getResolvedSymbol(node)
// CommonJS module.exports is never readonly
if expressionSymbol.Flags&ast.SymbolFlagsModuleExports != 0 {
return false
}
// references through namespace import should be readonly
if expressionSymbol.Flags&ast.SymbolFlagsAlias != 0 {
declaration := c.getDeclarationOfAliasSymbol(expressionSymbol)
return declaration != nil && ast.IsNamespaceImport(declaration)
}
}
}
if c.isReadonlySymbol(symbol) {
// Allow assignments to readonly properties within constructors of the same class declaration.
if symbol.Flags&ast.SymbolFlagsProperty != 0 && ast.IsAccessExpression(expr) && expr.Expression().Kind == ast.KindThisKeyword {
Expand All @@ -26627,6 +26612,21 @@ func (c *Checker) isAssignmentToReadonlyEntity(expr *ast.Node, symbol *ast.Symbo
}
return true
}
if ast.IsAccessExpression(expr) {
// references through namespace import should be readonly
node := ast.SkipParentheses(expr.Expression())
if ast.IsIdentifier(node) {
expressionSymbol := c.getResolvedSymbol(node)
// CommonJS module.exports is never readonly
if expressionSymbol.Flags&ast.SymbolFlagsModuleExports != 0 {
return false
}
if expressionSymbol.Flags&ast.SymbolFlagsAlias != 0 {
declaration := c.getDeclarationOfAliasSymbol(expressionSymbol)
return declaration != nil && ast.IsNamespaceImport(declaration)
}
}
}
return false
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
b.ts(3,5): error TS2540: Cannot assign to 'a' because it is a read-only property.


==== a.ts (0 errors) ====
const foo = {
a: 1
}

export default foo as Readonly<typeof foo>

==== b.ts (1 errors) ====
import foo from './a'

foo.a = 2
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.

30 changes: 30 additions & 0 deletions testdata/baselines/reference/compiler/readonlyDefaultExport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//// [tests/cases/compiler/readonlyDefaultExport.ts] ////

//// [a.ts]
const foo = {
a: 1
}

export default foo as Readonly<typeof foo>

Comment on lines +8 to +9
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

Error: Unexpected token

Suggested change
export default foo as Readonly<typeof foo>
const readonlyFoo = foo as Readonly<typeof foo>;
export default readonlyFoo;

Copilot uses AI. Check for mistakes.
//// [b.ts]
import foo from './a'

foo.a = 2


//// [a.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const foo = {
a: 1
};
exports.default = foo;
//// [b.js]
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const a_1 = __importDefault(require("./a"));
a_1.default.a = 2;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//// [tests/cases/compiler/readonlyDefaultExport.ts] ////

=== a.ts ===
const foo = {
>foo : Symbol(foo, Decl(a.ts, 0, 5))

a: 1
>a : Symbol(a, Decl(a.ts, 0, 13))
}

export default foo as Readonly<typeof foo>
>foo : Symbol(foo, Decl(a.ts, 0, 5))
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>foo : Symbol(foo, Decl(a.ts, 0, 5))

=== b.ts ===
import foo from './a'
>foo : Symbol(foo, Decl(b.ts, 0, 6))

foo.a = 2
>foo.a : Symbol(a, Decl(a.ts, 0, 13))
>foo : Symbol(foo, Decl(b.ts, 0, 6))
>a : Symbol(a, Decl(a.ts, 0, 13))

28 changes: 28 additions & 0 deletions testdata/baselines/reference/compiler/readonlyDefaultExport.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [tests/cases/compiler/readonlyDefaultExport.ts] ////

=== a.ts ===
const foo = {
>foo : { a: number; }
>{ a: 1} : { a: number; }

a: 1
>a : number
>1 : 1
}

export default foo as Readonly<typeof foo>
>foo as Readonly<typeof foo> : Readonly<{ a: number; }>
>foo : { a: number; }
>foo : { a: number; }

=== b.ts ===
import foo from './a'
>foo : Readonly<{ a: number; }>

foo.a = 2
>foo.a = 2 : 2
>foo.a : any
>foo : Readonly<{ a: number; }>
>a : any
>2 : 2

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
typedefModuleExportsIndirect1.js(3,8): error TS2540: Cannot assign to 'exports' because it is a read-only property.


==== typedefModuleExportsIndirect1.js (1 errors) ====
/** @typedef {{ a: 1, m: 1 }} C */
const dummy = 0;
module.exports = dummy;
~~~~~~~
!!! error TS2540: Cannot assign to 'exports' because it is a read-only property.
==== use.js (0 errors) ====
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
/** @type {C} */
var c

Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,3 @@ type C = import('./typedefModuleExportsIndirect1').C;
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
/** @type {C} */
declare var c: C;


//// [DtsFileErrors]


dist/typedefModuleExportsIndirect1.d.ts(5,1): error TS2309: An export assignment cannot be used in a module with other exported elements.
dist/typedefModuleExportsIndirect1.d.ts(5,10): error TS2304: Cannot find name 'dummy'.
dist/use.d.ts(1,52): error TS2694: Namespace 'unknown' has no exported member 'C'.


==== dist/typedefModuleExportsIndirect1.d.ts (2 errors) ====
export type C = {
a: 1;
m: 1;
};
export = dummy;
~~~~~~~~~~~~~~~
!!! error TS2309: An export assignment cannot be used in a module with other exported elements.
~~~~~
!!! error TS2304: Cannot find name 'dummy'.

==== dist/use.d.ts (1 errors) ====
type C = import('./typedefModuleExportsIndirect1').C;
~
!!! error TS2694: Namespace 'unknown' has no exported member 'C'.
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
/** @type {C} */
declare var c: C;

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ const dummy = 0;

module.exports = dummy;
>module.exports = dummy : 0
>module.exports : 0
>module.exports : any
>module : { readonly dummy: 0; }
>exports : 0
>exports : any
>dummy : 0

=== use.js ===
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
typedefModuleExportsIndirect2.js(3,8): error TS2540: Cannot assign to 'exports' because it is a read-only property.


==== typedefModuleExportsIndirect2.js (1 errors) ====
/** @typedef {{ a: 1, m: 1 }} C */
const f = function() {};
module.exports = f;
~~~~~~~
!!! error TS2540: Cannot assign to 'exports' because it is a read-only property.
==== use.js (0 errors) ====
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
/** @type {C} */
var c

Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,3 @@ type C = import('./typedefModuleExportsIndirect2').C;
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
/** @type {C} */
declare var c: C;


//// [DtsFileErrors]


dist/typedefModuleExportsIndirect2.d.ts(5,1): error TS2309: An export assignment cannot be used in a module with other exported elements.
dist/typedefModuleExportsIndirect2.d.ts(5,10): error TS2304: Cannot find name 'f'.
dist/use.d.ts(1,52): error TS2694: Namespace 'unknown' has no exported member 'C'.


==== dist/typedefModuleExportsIndirect2.d.ts (2 errors) ====
export type C = {
a: 1;
m: 1;
};
export = f;
~~~~~~~~~~~
!!! error TS2309: An export assignment cannot be used in a module with other exported elements.
~
!!! error TS2304: Cannot find name 'f'.

==== dist/use.d.ts (1 errors) ====
type C = import('./typedefModuleExportsIndirect2').C;
~
!!! error TS2694: Namespace 'unknown' has no exported member 'C'.
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
/** @type {C} */
declare var c: C;

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ const f = function() {};

module.exports = f;
>module.exports = f : () => void
>module.exports : () => void
>module.exports : any
>module : { readonly f: () => void; }
>exports : () => void
>exports : any
>f : () => void

=== use.js ===
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
typedefModuleExportsIndirect3.js(3,8): error TS2540: Cannot assign to 'exports' because it is a read-only property.


==== typedefModuleExportsIndirect3.js (1 errors) ====
/** @typedef {{ a: 1, m: 1 }} C */
const o = {};
module.exports = o;
~~~~~~~
!!! error TS2540: Cannot assign to 'exports' because it is a read-only property.
==== use.js (0 errors) ====
/** @typedef {import('./typedefModuleExportsIndirect3').C} C */
/** @type {C} */
var c

Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,3 @@ type C = import('./typedefModuleExportsIndirect3').C;
/** @typedef {import('./typedefModuleExportsIndirect3').C} C */
/** @type {C} */
declare var c: C;


//// [DtsFileErrors]


dist/typedefModuleExportsIndirect3.d.ts(5,1): error TS2309: An export assignment cannot be used in a module with other exported elements.
dist/typedefModuleExportsIndirect3.d.ts(5,10): error TS2304: Cannot find name 'o'.
dist/use.d.ts(1,52): error TS2694: Namespace 'unknown' has no exported member 'C'.


==== dist/typedefModuleExportsIndirect3.d.ts (2 errors) ====
export type C = {
a: 1;
m: 1;
};
export = o;
~~~~~~~~~~~
!!! error TS2309: An export assignment cannot be used in a module with other exported elements.
~
!!! error TS2304: Cannot find name 'o'.

==== dist/use.d.ts (1 errors) ====
type C = import('./typedefModuleExportsIndirect3').C;
~
!!! error TS2694: Namespace 'unknown' has no exported member 'C'.
/** @typedef {import('./typedefModuleExportsIndirect3').C} C */
/** @type {C} */
declare var c: C;

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ const o = {};

module.exports = o;
>module.exports = o : {}
>module.exports : {}
>module.exports : any
>module : { readonly o: {}; }
>exports : {}
>exports : any
>o : {}

=== use.js ===
Expand Down
Loading
Loading