Initial commit

This commit is contained in:
evil bocchi
2025-04-06 18:00:58 +08:00
committed by GitHub
commit 269ee8b777
9 changed files with 3241 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"jsx": true,
"useJSXTextNode": true,
"ecmaVersion": 2018,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": [
"@typescript-eslint",
"prettier"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"rules": {
"prettier/prettier": [
"warn",
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 120,
"tabWidth": 4,
"useTabs": true
}
],
"@typescript-eslint/no-explicit-any": [
"warn",
{
"ignoreRestArgs": true
}
],
"@typescript-eslint/no-non-null-assertion": [
"off"
]
}
}
+2
View File
@@ -0,0 +1,2 @@
out
node_modules
+15
View File
@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2020, Fireboltofdeath
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+38
View File
@@ -0,0 +1,38 @@
# rbxts-transformer-services
This is a [demo transformer](#template) that converts @rbxts/services imports into plain GetService calls for increased legibility.
## Example
```ts
// input.ts
import { Players, ServerScriptService } from "@rbxts/services";
print(Players.LocalPlayer);
print(ServerScriptService.GetChildren().size());
```
```lua
-- output.lua
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
print(Players.LocalPlayer)
print(#ServerScriptService:GetChildren())
```
# Template
This transformer is intended to be used as a template for those who are interested in creating their own transformers in roblox-ts.
A necessary resource for those starting out with transformers is [ts-ast-viewer](https://ts-ast-viewer.com/). It shows you the result of AST, relevant properties, symbol information, type information and it automatically generates factory code for nodes. For example, you can see the code this transformer generates [here](https://ts-ast-viewer.com/#code/MYewdgzgLgBACgGwIYE8CmAnCMC8MDmSAtmgHQDiaUAypgG4CWwaAFAESKqYRsCUANACgYImAHUQGANYQADkma4CxMpRr0mrNhIxQZ85nwDcQA).
I'd also recommend downloading the [TypeScript repo](https://github.com/microsoft/TypeScript) locally as it's extremely helpful when you're using undocumented (the majority of the compiler API), internal or uncommon APIs.
Transformers mutate the TypeScript [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) by replacing parts of the AST with new nodes. Transformers are also able to utilize symbol and type information giving you access to TypeScript's advanced control flow analysis.
## Other Transformers
Here's a list of transformers if you want to see how they handle working with parts of the TypeScript compiler api not shown here (e.g symbols or types).
- [rbxts-transform-debug](https://github.com/roblox-aurora/rbxts-transform-debug) by [@roblox-aurora](https://github.com/roblox-aurora)
- [rbxts-transform-env](https://github.com/roblox-aurora/rbxts-transform-env) by [@roblox-aurora](https://github.com/roblox-aurora)
- [Flamework](https://github.com/rbxts-flamework/transformer) by [@rbxts-flamework](https://github.com/rbxts-flamework)
One other source you may goto for learning about transformers is actually [roblox-ts](https://github.com/roblox-ts/roblox-ts/tree/master/src/TSTransformer) itself. This generates a Luau AST instead of a TS AST but it may still be a useful resource for learning about the compiler api.
+2966
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
{
"name": "rbxts-transformer-services",
"version": "1.1.1",
"description": "A transformer that converts @rbxts/services imports into plain GetService calls.",
"main": "out/index.js",
"author": "Fireboltofdeath <fireboltofpublic@gmail.com> (https://github.com/Fireboltofdeath)",
"license": "ISC",
"repository": {
"type": "git",
"url": "https://github.com/FireTS/rbxts-transformer-services.git"
},
"peerDependencies": {
"typescript": "^5.2.2"
},
"scripts": {
"prepare": "npm run build",
"build": "tsc",
"watch": "tsc -w"
},
"devDependencies": {
"@types/node": "^18.18.6",
"@types/ts-expose-internals": "npm:ts-expose-internals@5.2.2",
"@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^4.9.1",
"eslint": "^7.15.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-prettier": "^3.2.0",
"prettier": "^2.2.1"
},
"files": [
"out"
]
}
+13
View File
@@ -0,0 +1,13 @@
import ts from "typescript";
import { TransformContext, TransformerConfig } from "./transformer";
/**
* The transformer entry point.
* This provides access to necessary resources and the user specified configuration.
*/
export default function (program: ts.Program, config: TransformerConfig) {
return (transformationContext: ts.TransformationContext): ((file: ts.SourceFile) => ts.Node) => {
const context = new TransformContext(program, transformationContext, config);
return (file) => context.transform(file);
};
}
+117
View File
@@ -0,0 +1,117 @@
import ts from "typescript";
/**
* This is the transformer's configuration, the values are passed from the tsconfig.
*/
export interface TransformerConfig {
_: void;
}
/**
* This is a utility object to pass around your dependencies.
*
* You can also use this object to store state, e.g prereqs.
*/
export class TransformContext {
public factory: ts.NodeFactory;
constructor(
public program: ts.Program,
public context: ts.TransformationContext,
public config: TransformerConfig,
) {
this.factory = context.factory;
}
/**
* Transforms the children of the specified node.
*/
transform<T extends ts.Node>(node: T): T {
return ts.visitEachChild(node, (node) => visitNode(this, node), this.context);
}
}
function visitImportDeclaration(context: TransformContext, node: ts.ImportDeclaration) {
const { factory } = context;
const path = node.moduleSpecifier;
const clause = node.importClause;
if (!clause) return node;
if (!ts.isStringLiteral(path)) return node;
if (path.text !== "@rbxts/services") return node;
const namedBindings = clause.namedBindings;
if (!namedBindings) return node;
if (!ts.isNamedImports(namedBindings)) return node;
return [
// We replace the import declaration instead of stripping it to prevent
// issues with isolated modules.
factory.updateImportDeclaration(
node,
undefined,
factory.createImportClause(false, undefined, factory.createNamedImports([])),
node.moduleSpecifier,
undefined,
),
// Creates a multi-variable statement as shown below.
//
// const Players = game.GetService("Players"),
// Workspace = game.GetService("Workspace");
factory.createVariableStatement(
undefined,
factory.createVariableDeclarationList(
namedBindings.elements.map((specifier) => {
const serviceName = specifier.propertyName ? specifier.propertyName.text : specifier.name.text;
const variableName = specifier.name;
return factory.createVariableDeclaration(
variableName,
undefined,
undefined,
factory.createCallExpression(
factory.createPropertyAccessExpression(factory.createIdentifier("game"), "GetService"),
undefined,
[factory.createStringLiteral(serviceName)],
),
);
}),
ts.NodeFlags.Const,
),
),
];
}
function visitStatement(context: TransformContext, node: ts.Statement): ts.Statement | ts.Statement[] {
// This is used to transform statements.
// TypeScript allows you to return multiple statements here.
if (ts.isImportDeclaration(node)) {
// We have encountered an import declaration,
// so we should transform it using a separate function.
return visitImportDeclaration(context, node);
}
return context.transform(node);
}
function visitExpression(context: TransformContext, node: ts.Expression): ts.Expression {
// This can be used to transform expressions
// For example, a call expression for macros.
return context.transform(node);
}
function visitNode(context: TransformContext, node: ts.Node): ts.Node | ts.Node[] {
if (ts.isStatement(node)) {
return visitStatement(context, node);
} else if (ts.isExpression(node)) {
return visitExpression(context, node);
}
// We encountered a node that we don't handle above,
// but we should keep iterating the AST in case we find something we want to transform.
return context.transform(node);
}
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "out",
"rootDir": "src",
"strict": true,
"lib": [
"ES2015",
"DOM"
],
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}