Skip to content

Add basic CommonJS support #748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Apr 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
93a5b2f
First draft
sandersn Mar 26, 2025
f329913
Merge branch 'main' into add-basic-commonjs-support
sandersn Mar 26, 2025
2dfca09
a few fixes from early testing
sandersn Mar 26, 2025
872ae19
So many changes, I don't even know
sandersn Apr 1, 2025
29a1403
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 1, 2025
be697d6
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 3, 2025
c203acb
test updates and tweaks
sandersn Apr 4, 2025
8b198fe
bind commonjs as export-only for now
sandersn Apr 4, 2025
8ae9f66
Allow module.exports at non-top-level
sandersn Apr 7, 2025
3dc17ac
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 7, 2025
fc2a5d7
re-delete unused baselines
sandersn Apr 7, 2025
f01b883
hereby format
sandersn Apr 7, 2025
2f19775
Lots of cleanup and fixes
sandersn Apr 8, 2025
d3141dc
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 9, 2025
94cd60e
remove unused baselines AGAIN
sandersn Apr 9, 2025
dda9a73
re-add missing baselines
sandersn Apr 10, 2025
174be26
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 10, 2025
81286b4
cosmetic polish
sandersn Apr 10, 2025
288ca86
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 18, 2025
e2f23c5
Remove cjsExportMerged support
sandersn Apr 18, 2025
15c1409
remove getCommonJSExportEquals entirely
sandersn Apr 18, 2025
4985943
Resolve properties of module.exports/exports
sandersn Apr 23, 2025
05a9e36
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 23, 2025
b77bdc1
remove bogus ESM error on const x=require
sandersn Apr 23, 2025
dbd8b24
hereby format+hereby lint
sandersn Apr 23, 2025
b2e0c0f
stricter, more consistent rules for const x=require
sandersn Apr 24, 2025
f9912a7
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 24, 2025
20f0198
hereby format
sandersn Apr 24, 2025
7d428ac
remove all those unused test baselines I guess
sandersn Apr 25, 2025
7ff9d6e
Merge branch 'main' into add-basic-commonjs-support
sandersn Apr 25, 2025
9ec25a0
ANOTHER one
sandersn Apr 25, 2025
4c5c54e
#@[*&$#[*$#
sandersn Apr 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
79 changes: 73 additions & 6 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ func (n *Node) Expression() *Node {
return n.AsThrowStatement().Expression
case KindExternalModuleReference:
return n.AsExternalModuleReference().Expression
case KindExportAssignment:
case KindExportAssignment, KindJSExportAssignment:
return n.AsExportAssignment().Expression
case KindDecorator:
return n.AsDecorator().Expression
Expand Down Expand Up @@ -532,7 +532,7 @@ func (n *Node) Type() *Node {
return n.AsJSDocNonNullableType().Type
case KindJSDocOptionalType:
return n.AsJSDocOptionalType().Type
case KindEnumMember, KindBindingElement, KindExportAssignment, KindBinaryExpression:
case KindEnumMember, KindBindingElement, KindExportAssignment, KindJSExportAssignment, KindBinaryExpression, KindCommonJSExport:
return nil
default:
funcLike := n.FunctionLikeData()
Expand Down Expand Up @@ -907,6 +907,10 @@ func (n *Node) AsExportAssignment() *ExportAssignment {
return n.data.(*ExportAssignment)
}

func (n *Node) AsCommonJSExport() *CommonJSExport {
return n.data.(*CommonJSExport)
}

func (n *Node) AsObjectLiteralExpression() *ObjectLiteralExpression {
return n.data.(*ObjectLiteralExpression)
}
Expand Down Expand Up @@ -4314,6 +4318,7 @@ func IsNamedImports(node *Node) bool {

// This is either an `export =` or an `export default` declaration.
// Unless `isExportEquals` is set, this node was parsed as an `export default`.
// If Kind is KindJSExportAssignment, it is a synthetic declaration for `module.exports =`.
type ExportAssignment struct {
StatementBase
DeclarationBase
Expand All @@ -4323,17 +4328,25 @@ type ExportAssignment struct {
Expression *Expression // Expression
}

func (f *NodeFactory) NewExportAssignment(modifiers *ModifierList, isExportEquals bool, expression *Expression) *Node {
func (f *NodeFactory) newExportOrJSExportAssignment(kind Kind, modifiers *ModifierList, isExportEquals bool, expression *Expression) *Node {
data := &ExportAssignment{}
data.modifiers = modifiers
data.IsExportEquals = isExportEquals
data.Expression = expression
return f.newNode(KindExportAssignment, data)
return f.newNode(kind, data)
}

func (f *NodeFactory) NewExportAssignment(modifiers *ModifierList, isExportEquals bool, expression *Expression) *Node {
return f.newExportOrJSExportAssignment(KindExportAssignment, modifiers, isExportEquals, expression)
}

func (f *NodeFactory) NewJSExportAssignment(expression *Expression) *Node {
return f.newExportOrJSExportAssignment(KindJSExportAssignment, nil /*modifiers*/, true, expression)
}

func (f *NodeFactory) UpdateExportAssignment(node *ExportAssignment, modifiers *ModifierList, expression *Expression) *Node {
if modifiers != node.modifiers || expression != node.Expression {
return updateNode(f.NewExportAssignment(modifiers, node.IsExportEquals, expression), node.AsNode(), f.hooks)
return updateNode(f.newExportOrJSExportAssignment(node.Kind, modifiers, node.IsExportEquals, expression), node.AsNode(), f.hooks)
}
return node.AsNode()
}
Expand All @@ -4347,7 +4360,7 @@ func (node *ExportAssignment) VisitEachChild(v *NodeVisitor) *Node {
}

func (node *ExportAssignment) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewExportAssignment(node.Modifiers(), node.IsExportEquals, node.Expression), node.AsNode(), f.AsNodeFactory().hooks)
return cloneNode(f.AsNodeFactory().newExportOrJSExportAssignment(node.Kind, node.Modifiers(), node.IsExportEquals, node.Expression), node.AsNode(), f.AsNodeFactory().hooks)
}

func (node *ExportAssignment) computeSubtreeFacts() SubtreeFacts {
Expand All @@ -4358,6 +4371,60 @@ func IsExportAssignment(node *Node) bool {
return node.Kind == KindExportAssignment
}

func IsJSExportAssignment(node *Node) bool {
return node.Kind == KindJSExportAssignment
}

func IsAnyExportAssignment(node *Node) bool {
return node.Kind == KindExportAssignment || node.Kind == KindJSExportAssignment
}

// CommonJSExport

type CommonJSExport struct {
StatementBase
DeclarationBase
ExportableBase
ModifiersBase
name *IdentifierNode
Initializer *Expression
}

func (f *NodeFactory) NewCommonJSExport(modifiers *ModifierList, name *IdentifierNode, initializer *Expression) *Node {
data := &CommonJSExport{}
data.modifiers = modifiers
data.name = name
data.Initializer = initializer
return newNode(KindCommonJSExport, data, f.hooks)
}

func (f *NodeFactory) UpdateCommonJSExport(node *CommonJSExport, modifiers *ModifierList, name *IdentifierNode, initializer *Expression) *Node {
if modifiers != node.modifiers || initializer != node.Initializer || name != node.name {
return updateNode(f.NewCommonJSExport(node.modifiers, name, initializer), node.AsNode(), f.hooks)
}
return node.AsNode()
}

func (node *CommonJSExport) ForEachChild(v Visitor) bool {
return visitModifiers(v, node.modifiers) || visit(v, node.name) || visit(v, node.Initializer)
}

func (node *CommonJSExport) VisitEachChild(v *NodeVisitor) *Node {
return v.Factory.UpdateCommonJSExport(node, v.visitModifiers(node.modifiers), v.visitNode(node.name), v.visitNode(node.Initializer))
}

func (node *CommonJSExport) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewCommonJSExport(node.Modifiers(), node.name, node.Initializer), node.AsNode(), f.AsNodeFactory().hooks)
}

func IsCommonJSExport(node *Node) bool {
return node.Kind == KindCommonJSExport
}

func (node *CommonJSExport) Name() *DeclarationName {
return node.name
}

// NamespaceExportDeclaration

type NamespaceExportDeclaration struct {
Expand Down
4 changes: 3 additions & 1 deletion internal/ast/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,10 @@ const (
KindJSDocImportTag
// Synthesized list
KindSyntaxList
// Synthesized JS nodes
// Reparsed JS nodes
KindJSTypeAliasDeclaration
KindJSExportAssignment
KindCommonJSExport
// Transformation nodes
KindNotEmittedStatement
KindPartiallyEmittedExpression
Expand Down
16 changes: 9 additions & 7 deletions internal/ast/kind_stringer_generated.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions internal/ast/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const (
InternalSymbolNameExportEquals = "export=" // Export assignment symbol
InternalSymbolNameDefault = "default" // Default export symbol (technically not wholly internal, but included here for usability)
InternalSymbolNameThis = "this"
InternalSymbolNameModuleExports = "module.exports"
)

func SymbolName(symbol *Symbol) string {
Expand Down
87 changes: 46 additions & 41 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ func isDeclarationStatementKind(kind Kind) bool {
KindImportEqualsDeclaration,
KindExportDeclaration,
KindExportAssignment,
KindJSExportAssignment,
KindNamespaceExportDeclaration:
return true
}
Expand Down Expand Up @@ -1304,7 +1305,7 @@ func GetElementOrPropertyAccessArgumentExpressionOrName(node *Node) *Node {
}

func GetElementOrPropertyAccessName(node *Node) string {
name := getElementOrPropertyAccessArgumentExpressionOrName(node)
name := GetElementOrPropertyAccessArgumentExpressionOrName(node)
if name == nil {
return ""
}
Expand Down Expand Up @@ -1351,10 +1352,10 @@ func GetNonAssignedNameOfDeclaration(declaration *Node) *Node {
switch declaration.Kind {
case KindBinaryExpression:
if IsFunctionPropertyAssignment(declaration) {
return getElementOrPropertyAccessArgumentExpressionOrName(declaration.AsBinaryExpression().Left)
return GetElementOrPropertyAccessArgumentExpressionOrName(declaration.AsBinaryExpression().Left)
}
return nil
case KindExportAssignment:
case KindExportAssignment, KindJSExportAssignment:
expr := declaration.AsExportAssignment().Expression
if IsIdentifier(expr) {
return expr
Expand Down Expand Up @@ -1414,22 +1415,6 @@ func IsFunctionPropertyAssignment(node *Node) bool {
return false
}
Copy link
Member Author

Choose a reason for hiding this comment

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

we have a private and public duplicate of this function so I deleted the private one


// Does not handle signed numeric names like `a[+0]` - handling those would require handling prefix unary expressions
// throughout late binding handling as well, which is awkward (but ultimately probably doable if there is demand)
func getElementOrPropertyAccessArgumentExpressionOrName(node *Node) *Node {
switch node.Kind {
case KindPropertyAccessExpression:
return node.Name()
case KindElementAccessExpression:
arg := SkipParentheses(node.AsElementAccessExpression().ArgumentExpression)
if IsStringOrNumericLiteralLike(arg) {
return arg
}
return node
}
panic("Unhandled case in getElementOrPropertyAccessArgumentExpressionOrName")
}

/**
* A declaration has a dynamic name if all of the following are true:
* 1. The declaration has a computed property name.
Expand Down Expand Up @@ -1495,10 +1480,6 @@ func IsEffectiveExternalModule(node *SourceFile, compilerOptions *core.CompilerO
return IsExternalModule(node) || (isCommonJSContainingModuleKind(compilerOptions.GetEmitModuleKind()) && node.CommonJSModuleIndicator != nil)
}
Copy link
Member Author

Choose a reason for hiding this comment

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

this is unused


func IsEffectiveExternalModuleWorker(node *SourceFile, moduleKind core.ModuleKind) bool {
return IsExternalModule(node) || (isCommonJSContainingModuleKind(moduleKind) && node.CommonJSModuleIndicator != nil)
}

func isCommonJSContainingModuleKind(kind core.ModuleKind) bool {
return kind == core.ModuleKindCommonJS || kind == core.ModuleKindNode16 || kind == core.ModuleKindNodeNext
}
Expand Down Expand Up @@ -1668,20 +1649,7 @@ func IsEnumConst(node *Node) bool {
}

func ExportAssignmentIsAlias(node *Node) bool {
Copy link
Member Author

Choose a reason for hiding this comment

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

I believe this was for syntactic module.exports= support and will never be used.

return isAliasableExpression(getExportAssignmentExpression(node))
}

func getExportAssignmentExpression(node *Node) *Node {
switch node.Kind {
case KindExportAssignment:
return node.AsExportAssignment().Expression
case KindBinaryExpression:
return node.AsBinaryExpression().Right
}
panic("Unhandled case in getExportAssignmentExpression")
}

func isAliasableExpression(e *Node) bool {
e := node.AsExportAssignment().Expression
return IsEntityNameExpression(e) || IsClassExpression(e)
}

Expand Down Expand Up @@ -2086,6 +2054,7 @@ func GetMeaningFromDeclaration(node *Node) SemanticMeaning {
KindImportEqualsDeclaration,
KindImportDeclaration,
KindExportAssignment,
KindJSExportAssignment,
KindExportDeclaration:
return SemanticMeaningAll

Expand Down Expand Up @@ -2474,22 +2443,27 @@ func IsNonLocalAlias(symbol *Symbol, excludes SymbolFlags) bool {
// An alias symbol is created by one of the following declarations:
//
// import <symbol> = ...
// const <symbol> = ... (JS only)
// const { <symbol>, ... } = ... (JS only)
// import <symbol> from ...
// import * as <symbol> from ...
// import { x as <symbol> } from ...
// export { x as <symbol> } from ...
// export * as ns <symbol> from ...
// export = <EntityNameExpression>
// export default <EntityNameExpression>
// module.exports = <EntityNameExpression> (JS only)
func IsAliasSymbolDeclaration(node *Node) bool {
switch node.Kind {
case KindImportEqualsDeclaration, KindNamespaceExportDeclaration, KindNamespaceImport, KindNamespaceExport,
KindImportSpecifier, KindExportSpecifier:
return true
case KindImportClause:
return node.AsImportClause().Name() != nil
case KindExportAssignment:
case KindExportAssignment, KindJSExportAssignment:
return ExportAssignmentIsAlias(node)
case KindVariableDeclaration, KindBindingElement:
return IsVariableDeclarationInitializedToRequire(node)
}
return false
}
Expand Down Expand Up @@ -2568,7 +2542,7 @@ func ForEachDynamicImportOrRequireCall(
lastIndex, size := findImportOrRequire(file.Text(), 0)
for lastIndex >= 0 {
node := GetNodeAtPosition(file, lastIndex, isJavaScriptFile && includeTypeSpaceImports)
if isJavaScriptFile && IsRequireCall(node, requireStringLiteralLikeArgument) {
if isJavaScriptFile && IsRequireCall(node) {
if cb(node, node.Arguments()[0]) {
return true
}
Expand All @@ -2595,7 +2569,8 @@ func ForEachDynamicImportOrRequireCall(
return false
}

func IsRequireCall(node *Node, requireStringLiteralLikeArgument bool) bool {
// IsVariableDeclarationInitializedToRequire should be used wherever parent pointers are set
func IsRequireCall(node *Node) bool {
if !IsCallExpression(node) {
return false
}
Expand All @@ -2606,7 +2581,7 @@ func IsRequireCall(node *Node, requireStringLiteralLikeArgument bool) bool {
if len(call.Arguments.Nodes) != 1 {
return false
}
return !requireStringLiteralLikeArgument || IsStringLiteralLike(call.Arguments.Nodes[0])
return IsStringLiteralLike(call.Arguments.Nodes[0])
}

func IsUnterminatedLiteral(node *Node) bool {
Expand Down Expand Up @@ -2664,6 +2639,36 @@ func GetPragmaArgument(pragma *Pragma, name string) string {
return ""
}

// Of the form: `const x = require("x")` or `const { x } = require("x")` or with `var` or `let`
// The variable must not be exported and must not have a type annotation, even a jsdoc one.
// The initializer must be a call to `require` with a string literal or a string literal-like argument.
func IsVariableDeclarationInitializedToRequire(node *Node) bool {
if !IsInJSFile(node) {
return false
}
if node.Kind == KindBindingElement {
node = node.Parent.Parent
}
if node.Kind != KindVariableDeclaration {
return false
}

return node.Parent.Parent.ModifierFlags()&ModifierFlagsExport == 0 &&
node.AsVariableDeclaration().Initializer != nil &&
node.Type() == nil &&
IsRequireCall(node.AsVariableDeclaration().Initializer)
}

func IsModuleExportsAccessExpression(node *Node) bool {
return (IsPropertyAccessExpression(node) || isLiteralLikeElementAccess(node)) &&
IsModuleIdentifier(node.Expression()) &&
GetElementOrPropertyAccessName(node) == "exports"
}

func isLiteralLikeElementAccess(node *Node) bool {
return node.Kind == KindElementAccessExpression && IsStringOrNumericLiteralLike(node.AsElementAccessExpression().ArgumentExpression)
}

func IsCheckJSEnabledForFile(sourceFile *SourceFile, compilerOptions *core.CompilerOptions) bool {
if sourceFile.CheckJsDirective != nil {
return sourceFile.CheckJsDirective.Enabled
Expand Down
Loading