Repository navigation
Libraries: import / export with pluggable cached import resolvers - #458
Merged
Merged
Conversation
…rs take their defaults A user method called with method syntax and named arguments, `obj.m(a = x)`, received the named arguments as one positional value; they now bind to the method's parameters after the receiver, as in the function-syntax call `m(obj, a = x)`. A built-in method name or a namespace receiver (`label.new(text = ...)`) is left alone. A parameter a named-argument call skips (`f(10, z = 7)` with a defaulted `y`) read as na: the gap was passed as an explicit `undefined`, which reaches the function wrapped as a series and bypasses the default. Codegen now fills it with the parameter's default value, which Pine restricts to a literal or a built-in variable, so it evaluates the same at the call site. A string parameter defaulting to `na` gets the empty string, as in the signature. Expected values are computed by hand from Pine's binding rules. Co-authored-by: Cursor <cursoragent@cursor.com>
`import <user>/<library>/<version> [as <alias>]` is parsed (the alias defaults to the library name), and so are `export` before functions, methods, types and enums, and the `library()` declaration, which now also runs as an indicator would. Library sources come from an import resolver set with `PineTS.setImportResolver`: a function `(path, info) => source`. Before transpiling, `Indicator.resolveImports` collects the script's libraries and theirs; a `CachedImportResolver` requests each path once, shared across runs and with request.security contexts. Ready-made resolvers: `ImportResolver.fromMap`, `fromFiles` (Node.js), `fromUrl`, `fromGitHub` (raw.githubusercontent.com, or the contents API with a token for private repositories) and `chain`. The linker (pineToJS/linker.ts) splices each library's declarations into the importing script's Pine AST where its imports stood, renamed with a `<user>$<library>$<version>$` prefix (`$` is not a Pine identifier character), and rewrites `alias.member` references: functions, `alias.Type.new()`, type annotations, `alias.Enum.field`, and `alias.method(obj)` as `obj.method()`. The rest of the pipeline sees ordinary user code, so each call site keeps its own `var` / `ta.*` state. Exported methods keep their name for method-syntax dispatch; private ones are renamed. Only declarations reachable from the script are linked, so a library's unresolved import only fails when the script uses code that needs it. An alias named like a built-in namespace keeps reaching the built-ins the library does not export. Docs: docs/libraries.md. Co-authored-by: Cursor <cursoragent@cursor.com>
The resolver API was closures returned by an `ImportResolver` object of factories, typed as a function-or-`{ resolve }` union, with caching added silently by an outside wrapper. It is rebuilt like the data providers:
- `IImportResolver` interface (`resolve`, `configure`), mirroring `IProvider`.
- `BaseImportResolver<TConfig>`: `resolve()` drives the cache (one request per import path, concurrent requests shared, errors not kept, optional `cacheDuration`, emptied by `configure()` / `clearCache()`) and the fail-early configuration check, and delegates to `_resolveNative()`, as `getMarketData()` delegates to `_getMarketDataNative()`.
- One class per source, each in its own folder with a typed config and `configure()`: `GitHubImportResolver`, `FileImportResolver`, `UrlImportResolver`, `MapImportResolver`, `ChainImportResolver`, and `FunctionImportResolver` for a plain function.
- `ImportResolver` registry of ready instances (`ImportResolver.GitHub.configure({ url, token })`) and `registerImportResolver`, mirroring `Provider`.
- `FileImportResolver` reads files with `fs` and is stubbed in browser builds, as `MockProvider` is (the rollup plugin now handles both).
Two wiring fixes come with it. A `request.*` secondary context inherits its parent's resolver in `markAsSecondary(parent)` instead of at each call site. An `Indicator` run with another resolver resolves its imports again, and transpiles again if the sources differ; it kept the first resolver's libraries.
Co-authored-by: Cursor <cursoragent@cursor.com>
…S README The usage guide gets a Libraries section (setImportResolver, the ImportResolver registry and classes, link to the Libraries page); the README lists libraries in its features, documentation links and roadmap; AGENTS.md shows the linker in the Stage 1 pipeline and src/imports/ and tests/imports/ in the file tree; the pineToJS README describes the linking step. The Libraries page tables are aligned. Co-authored-by: Cursor <cursoragent@cursor.com>
alexgrover
marked this pull request as ready for review
October 22, 2026 11:35
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: fd101a66-4505-430c-ab27-45115136160f) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Pine Script library support:
import <user>/<library>/<version> [as <alias>],export, and thelibrary()declaration. Library sources come from a pluggable import resolver, cached per import path. Also fixes two existing named-argument bugs that this work exposed (separate commit).Opened as a draft for review of the resolver API and of the linking approach.
Usage
Import resolvers follow the data provider pattern: a registry of ready instances, or direct instantiation.
GitHubImportResolverImportResolver.GitHubFileImportResolverImportResolver.FileMockProvider)UrlImportResolverImportResolver.UrlMapImportResolverImportResolver.MapChainImportResolverFunctionImportResolver(path, info) => source;setImportResolver(fn)wraps oneCustom resolvers implement
IImportResolveror extendBaseImportResolver. Docs:docs/libraries.md.How it works
1. Resolution (async, before transpiling).
PineTS.run()/stream()callIndicator.resolveImports(resolver), which reads the script's imports from its tokens (no full parse), asks the resolver for each, and recurses into each library's own imports. The result ({ path: source }) is passed to the synchronoustranspile(source, { libraries }). AnIndicatorrun with another resolver resolves again, and transpiles again if the sources differ.The resolver classes mirror the providers: an
IImportResolverinterface (resolve,configure), and an abstractBaseImportResolver<TConfig>whoseresolve()handles caching and the fail-early configuration check, then delegates to_resolveNative()(asBaseProvider.getMarketData()delegates to_getMarketDataNative()). The cache keeps one promise per import path: concurrent requests share one call, sources and "unknown library" (null) results are kept (optionalcacheDuration), a failure is retried on the next run, andconfigure()/clearCache()empty it. Sharing a resolver instance shares its cache.request.security/request.security_lower_tfsecondary contexts inherit the parent's resolver inmarkAsSecondary(parent), so they never fetch again.2. Linking (sync, in
pineToJS, on the Pine AST).src/transpiler/pineToJS/linker.tsparses each library and splices its declarations into the importing script's AST where itsimportstatements stood. The rest of the pipeline then only sees ordinary user functions, methods, types and enums, so library code gets per-call-sitevar/ta.*state, collision renames and method dispatch with no change downstream.<user>$<library>$<version>$<name>.$is not a Pine identifier character, so linked names cannot collide with the script's own or another library's.alias.memberreferences are rewritten:alias.f(),alias.Type.new()/.copy(),alias.Typein annotations (alias.Type p,array<alias.Type>),alias.Enum.field, andalias.method(obj, ...)→obj.method(...).talibrary importsTradingView/RelativeValue/3; without it every function exceptrelativeVolumeworks, andta.relativeVolumefails withLibrary "TradingView/ta/14" uses TVrv.averageAtTime() from "TradingView/RelativeValue/3", which could not be resolved.import TradingView/ta/12 as ta, used by 31 scripts of the last community sweep) reaches the library for the members it exports and the built-ins for everything else.Why not append the transpiled library functions to the script and rely on hoisting: functions would hoist, but types and enums compile to
conststatements that do not, and linking before codegen is what gives library code the per-call-site state and renames of script code.Parser.
import/exportare top-level statements (in a local scope:"import" is only allowed in the global scope);import = 1andexport(x) => xstill give TradingView's reserved-name error. Exported function names now count as top-level function names, sofloat ema2 = ema2(...)insideexport ema2(...)no longer shadows the function.library()runs as an indicator declaration, so a library script also runs on its own.Named-argument fixes (first commit)
Found while comparing a toolkit's linked and inlined runs: a library method called with named arguments returned the wrong value on every bar. Both bugs also affect plain scripts.
trig.enc(a = false, b = true, c = true)withmethod enc(map<string, bool> m, bool a, bool b, bool c) => (a ? 4 : 0) + (b ? 2 : 0) + (c ? 1 : 0)trig.enc(false, c = true, b = false)f(10, z = 7)withf(float base, float x = 1, float y = 2, float z = 3) => base + x * 100 + y * 10 + zx,yread as na)a.total(z = 7), same shape as a methodCodegen now maps named arguments of
obj.method(...)onto the method's parameters after the receiver (not for built-in method names or namespace receivers likelabel.new(text = ...)), and fills a skipped parameter with its default value — Pine restricts defaults to literals and built-in variables, so evaluating it at the call site is equivalent. A string parameter defaulting tonagets"", as in the signature.Verification
dev):tests/transpiler/import-export-parsing.test.ts: import paths and aliases, invalid paths, local-scope errors, export forms, a library running on its own.tests/imports/library-linking.test.ts: per-call-site state (two calls of a library EMA vs the built-in, twovarcounters), types / enums / methods both syntaxes, named arguments and defaults, no collisions with the script's own names, tree shaking, built-in-namespace alias, imported enum ininput.enum, nested imports, unresolved nested import, error cases,request.security(slice path and full-script fallback, library fetched once).tests/imports/import-resolver.test.ts:BaseImportResolvercache (dedup,nullkept, errors retried,cacheDuration,configure/clearCache) and configuration check, the registry,Maplookup order,Chain,Url,GitHubURL forms andconfiguremerging with a stubbedfetch,File,Function, recursiveresolveImports, a resolver shared by two instances fetching once, anIndicatorrun with two resolvers.tests/transpiler/method-named-args.test.ts: the table above.talibrary imported asta:dema,tema,trima,aroon,changePercent,highestSince,stochFullequal the same formulas written with built-ins on all 577 bars.@valuewhenmatches the inlined run on all bars, and its simple conditions match the same conditions written directly in Pine./blob/URL.tsc --noEmitreports the same 881 errors ondevand on this branch, none in the changed files;tsc -p tsconfig.dts.json(src) is clean.dev); the CJS and ES bundles link imports withMapImportResolver,FileImportResolverandImportResolver.File; the browser bundles contain nofsand get theFileImportResolverstub.Docs
docs/libraries.md(new page): import resolvers (registry vs direct instantiation, one section per resolver with its config table, chaining, caching, custom resolvers viaIImportResolver/BaseImportResolver), what is supported, limitations.docs/initialization-and-usage.md: a Libraries section (setImportResolver).README.md: libraries in the features, documentation links and roadmap.docs/lang-coverage.md: Imports → done;docs/api-coverage:library()✅.AGENTS.md: the linker in the Stage 1 pipeline,src/imports/andtests/imports/in the file tree;src/transpiler/pineToJS/README.md: the linking step.CHANGELOG.md: Unreleased entries.Limitations
and/or,const intdivision), which matters when a v6 script imports a v5 library.export alias.Type f() =>) does not parse yet.Found while testing, unrelated to imports (they fail the same way with the library code inlined): a UDT named like a built-in namespace (
type strategy) breaks thestrategy()declaration call, and one toolkit's tuple-returning function called insiderequest.securitythrowsCannot read properties of undefined (reading '1').Note
Medium Risk
Large transpiler/runtime surface (linking, resolution, caching) affects any script using imports; named-argument codegen changes touch all user methods/functions with named args.
Overview
Adds Pine Script libraries:
import/export,library(), and a new linker step inpineToJSthat splices resolved library sources into the script AST (prefixed renames, reachability-based linking, nested imports). Before transpile,PineTS.setImportResolverandresolveImportsfetch sources via pluggable, cached resolvers (GitHub, file, URL, map, chain, or a function), mirroring the market-data provider pattern; secondaryrequest.securitycontexts inherit the same resolver.Codegen fixes for named arguments on user method calls (
obj.m(a = 1)) and for skipped parameters using defaults instead ofna.Docs, API coverage, browser stubs for Node-only
FileImportResolver, and tests undertests/imports/and transpiler suites.Reviewed by Cursor Bugbot for commit a51b0a1. Bugbot is set up for automated code reviews on this repo. Configure here.