diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 42da0a6..512c823 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -15,24 +15,46 @@ Node 20 or newer is required. ## Where things live -| Change you want to make | Edit this | -| --------------------------------------- | ------------------------------------------------------ | -| Add or remove a built-in function | `src/data/functions.json` | -| Add or remove a built-in variable | `src/data/variables.json` | -| Add or remove a built-in constant | `src/data/constants.json` | -| Add a compiler annotation (`//@...`) | `src/data/annotations.json` | -| Add a keyword, type or change a scope | `src/grammar.mjs` | -| Change folding, brackets or indentation | `language-configuration.json` | -| Add a snippet | `snippets/pinescript.code-snippets` | -| Never | `syntaxes/pinescript.tmLanguage.json` (generated file) | +| Change you want to make | Edit this | +| --------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| Add or remove a built-in function | `src/data/functions.json` | +| Add or remove a built-in variable | `src/data/variables.json` | +| Add or remove a built-in constant | `src/data/constants.json` | +| Add a compiler annotation (`//@...`) | `src/data/annotations.json` | +| Add a keyword, type or change a scope | `src/grammar.mjs` | +| Change folding, brackets or indentation | `language-configuration.json` | +| Add a snippet | `snippets/pinescript.code-snippets` | +| Completion, hover, commands, inference | `src/extension/core/*` (logic) and `src/extension/providers/*`, `src/extension/commands/*` (VS Code glue) | +| Change a theme color | `themes/*.json` | +| Never | `syntaxes/pinescript.tmLanguage.json`, `src/data/reference.json`, `dist/` (generated) | The data files are grouped by namespace. A function `ta.sma` goes under the `"ta"` key as `"sma"`. Identifiers without a namespace go under the `""` key. Nested namespaces such as `strategy.closedtrades` are their own key. +`src/extension/core` never imports `vscode`; that is what makes it testable with vitest. Providers and commands only translate between VS Code types and core types. + +## Working on the extension + +```sh +npm run watch # rebuild dist/extension.cjs on change +``` + +Press F5 to launch an Extension Development Host, then reload it after each rebuild. Tests for extension logic go in `tests/core`; `tests/core/fake-vscode.ts` stands in for the `vscode` module so providers can run under vitest too. + +## Refreshing the reference data + +```sh +npm install --no-save playwright-core +npx playwright-core install chromium +npm run scrape:reference +npm run build:grammar:check # lists built-ins to add to or remove from src/data/*.json +``` + ## Workflow 1. Make your change in `src/`. 2. Run `npm run build` to regenerate the grammar. 3. Add or update a test: + - `tests/core/*.test.ts` for extension logic. - `tests/unit/*.pine` for a targeted scope assertion (see the existing files for the syntax; `^` markers under a token followed by the expected scopes). - `tests/snapshots/*.pine` for a realistic script. Run `npm run test:snap:update` after intentional changes and review the `.snap` diff. 4. Run `npm test`. CI runs the same command and also fails if the generated grammar is out of date. @@ -43,10 +65,21 @@ The data files are grouped by namespace. A function `ta.sma` goes under the `"ta Built-in identifiers come from the [Pine Script v6 reference](https://www.tradingview.com/pine-script-reference/v6/). Please link the reference entry when adding one. Identifiers that were removed from the language are removed from the grammar too; we do not keep deprecated v3/v4 names. +## Manual checklist before a release + +- Completion after `ta.`, inside `plot(`, after `//@`, after `import ` +- Hover on `close`, `ta.sma`, a user function, an import line +- Signature help through all parameters of `plot(` +- Outline shows functions, types, enums, variables +- Generate Docstring on a function, a type and an enum +- Add Type Annotations on `tests/snapshots/strategy-v6.pine` +- Both themes on `tests/snapshots/strategy-v6.pine` +- With `pinescript.diagnostics.remote` on: an undeclared identifier is underlined + ## Releasing Maintainers only. 1. Update `CHANGELOG.md` and bump `version` in `package.json`. -2. Commit, then tag: `git tag v2.x.y && git push --tags`. +2. Commit, then tag: `git tag v3.x.y && git push --tags`. 3. The release workflow builds the `.vsix`, attaches it to a GitHub release and publishes to the Marketplace when the `VSCE_PAT` secret is configured. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 30a2adf..f287878 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ A short Pine snippet or screenshot showing the highlighting before and after is **Checklist** - [ ] I edited `src/` (not the generated `syntaxes/pinescript.tmLanguage.json`) and ran `npm run build` -- [ ] I added or updated a test in `tests/unit` or `tests/snapshots` and `npm test` passes +- [ ] I added or updated a test in `tests/core`, `tests/unit` or `tests/snapshots` and `npm test` passes - [ ] I ran `npm run format` - [ ] New built-ins link to their entry in the Pine Script v6 reference - [ ] `CHANGELOG.md` has an entry under **Unreleased** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b3da64..595be69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,14 +26,29 @@ jobs: run: npm run format:check - name: Verify generated grammar is committed - run: npm run build:check + run: npm run build:grammar:check - - name: Run grammar tests - run: npm test + - name: Verify themes cover every grammar scope + run: npm run check:themes + + - name: Type check + run: npm run typecheck + + - name: Core tests + run: npm run test:core + + - name: Grammar tests + run: npm run test:unit && npm run test:snap + + - name: Bundle extension + run: npm run build:extension - name: Package extension run: npx vsce package --no-dependencies --out pine-script-syntax-highlighter.vsix + - name: Verify bundle in package + run: unzip -l pine-script-syntax-highlighter.vsix | grep -q 'extension/dist/extension.cjs' + - uses: actions/upload-artifact@v5 with: name: vsix diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0dab32d..c34ad84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,7 @@ jobs: - run: npm ci - run: npm test + - run: npm run build:extension - name: Verify tag matches package version run: | @@ -35,6 +36,9 @@ jobs: - name: Package run: npx vsce package --no-dependencies --out "pine-script-syntax-highlighter-${GITHUB_REF_NAME}.vsix" + - name: Verify bundle in package + run: unzip -l *.vsix | grep -q 'extension/dist/extension.cjs' + - name: Create GitHub release uses: softprops/action-gh-release@v2 with: diff --git a/.gitignore b/.gitignore index 0a48646..6c6beb9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules/ .DS_Store npm-debug.log* .gstack/ +dist/ diff --git a/.prettierignore b/.prettierignore index b1a37aa..c46ad91 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,5 @@ node_modules/ *.vsix CHANGELOG.md package-lock.json +dist/ +src/data/reference.json diff --git a/.vscodeignore b/.vscodeignore index 8bf4053..dbd2793 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,14 +1,16 @@ .vscode/** -.vscode-test/** .github/** +.gstack/** node_modules/** src/** scripts/** tests/** -.gitignore +docs/** .editorconfig +.gitignore .prettierrc .prettierignore +tsconfig.json +vitest.config.ts package-lock.json *.vsix -.gstack/** diff --git a/CHANGELOG.md b/CHANGELOG.md index fff0705..b345315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and ## [Unreleased] +## [3.0.0] - 2026-09-07 + +The extension becomes a full language extension. Highlighting is unchanged; everything below is new. + +### Added + +- Completion for built-ins, keywords, types, user symbols, named arguments, annotations and import paths, with documentation and signatures. +- Hover documentation for built-ins, user functions, types, enums, variables and imports. +- Signature help with overloads and named-argument awareness. +- Document outline (functions, methods, types, enums, top-level variables). +- Workspace library indexing and published TradingView library lookup for `import` completion and hover. +- Opt-in diagnostics from the TradingView compiler (`pinescript.diagnostics.remote`, off by default). +- Commands: New Indicator, New Strategy, New Library, Generate Docstring (with a code action), Add Type Annotations, Open Reference. +- Pine Dark and Pine Light color themes. +- `src/data/reference.json`, generated from the v6 reference, and a build check that keeps it in step with the grammar data. + +### Changed + +- Minimum VS Code version is 1.96. +- The package now ships a bundled extension entry point (`dist/extension.cjs`) built with esbuild. +- `npm run build` builds both the grammar and the extension; grammar-only scripts are `build:grammar` and `build:grammar:check`. +- `line.set_xy` was removed from the grammar data; it is not part of Pine Script v6. + ## [2.0.1] - 2026-09-06 ### Fixed @@ -63,7 +86,8 @@ Complete rewrite targeting Pine Script v6. The grammar is now generated from dat - Language operators, built-in variables and built-in functions for Pine Script v3/v4. -[Unreleased]: https://github.com/yankikucuk/pine-script-syntax-highlighter/compare/v2.0.1...HEAD +[Unreleased]: https://github.com/yankikucuk/pine-script-syntax-highlighter/compare/v3.0.0...HEAD +[3.0.0]: https://github.com/yankikucuk/pine-script-syntax-highlighter/compare/v2.0.1...v3.0.0 [2.0.1]: https://github.com/yankikucuk/pine-script-syntax-highlighter/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/yankikucuk/pine-script-syntax-highlighter/compare/v1.0.5...v2.0.0 [1.0.5]: https://github.com/yankikucuk/pine-script-syntax-highlighter/releases/tag/v1.0.5 diff --git a/README.md b/README.md index f685fcc..b284df2 100644 --- a/README.md +++ b/README.md @@ -21,42 +21,88 @@ ## Features -- **Pine Script v6 coverage.** Every namespace from the official language reference: `ta`, `math`, `str`, `array`, `matrix`, `map`, `request`, `strategy`, `input`, `label`, `line`, `box`, `table`, `polyline`, `log`, `runtime`, `chart`, `footprint` and the rest. Built-in functions, variables and constants are highlighted with distinct scopes. -- **Modern language constructs.** `switch`, `while`, `once`, `for ... in`, `method`, `type`, `enum`, `import ... as`, `export`, `varip`, type qualifiers (`series`, `simple`, `const`, `input`) and generics such as `array` or `map`. -- **Compiler annotations.** `//@version=6`, `//@description`, `//@function`, `//@param`, `//@returns`, `//@type`, `//@field`, `//@variable`, `//@enum` and `//@strategy_alert_message` are highlighted inside comments. -- **Strings done right.** Single, double and the v6 triple-quoted multiline strings, escape sequences and `str.format` placeholders like `{0,number,#.##}`. -- **Numbers and colors.** Integers, floats, exponents and hex colors with optional alpha (`#RRGGBBAA`). -- **User code.** Function and method definitions, named arguments, tuple destructuring (`[a, b] = f()`), user-defined type constructors (`Point.new()`) and member access. -- **Editor support.** Indentation-based folding, auto-closing quotes and brackets, `// region` / `// endregion` markers, four-space indentation defaults for `.pine` files. -- **Snippets.** `indicator`, `strategy`, `library`, `fn`, `method`, `type`, `enum`, `if`, `switch`, `for`, `forin`, `while`, `once`, `input.int`, `input.float`, `request.security`, `plot`, `alertcondition` and more. +- **Completion.** Built-in functions, variables and constants from every v6 namespace, keywords, types, your own functions, types, enums and variables, named arguments inside calls, `//@` annotations, and `import` paths. Function items insert a call and open parameter hints. +- **Hover documentation.** Signature, description, parameters, return value and a link to the reference entry for built-ins; declaration and `//@` docs for your own symbols; a library card on `import` lines. +- **Signature help.** Overloads and the active parameter, including named arguments. +- **Outline.** Functions, methods, types with fields, enums with members and top-level variables in the Outline view and breadcrumbs. +- **Libraries.** Workspace files that call `library()` are indexed: their exports complete after the import alias and show up on hover. With `pinescript.libraries.remote` on, `import` completion also lists published TradingView libraries and hover shows their exports. +- **Compiler diagnostics (opt-in).** Set `pinescript.diagnostics.remote` to `true` to send the document to the TradingView compiler and see its errors and warnings inline. +- **Commands.** New Indicator / Strategy / Library from a template, Generate Docstring (also a lightbulb on declarations), Add Type Annotations, Open Reference for the built-in under the cursor. +- **Themes.** Pine Dark and Pine Light, tuned for every scope the grammar produces. +- **Highlighting, snippets and editor support** as before: the full v6 vocabulary, annotations, triple-quoted strings, format placeholders, hex colors, folding, auto-closing pairs and four-space indentation for `.pine` files. Files ending in `.pine` or `.pinescript`, or starting with `//@version=`, are recognized automatically. +### Pine Script v6 compatibility + +The grammar and the documentation data are generated from the v6 reference, so v6 behaviors such as dynamic `request.*()` calls with `series string` arguments, short-circuit `and`/`or`, point-based text sizes, `text.format_bold` / `text.format_italic`, order trimming and negative array indices are covered wherever they have syntax to highlight or document. They are compiler behaviors; the extension does not emulate them. + +## Commands + +| Command | What it does | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Pine Script: New Indicator | Opens an untitled document from the indicator template | +| Pine Script: New Strategy | Opens an untitled document from the strategy template | +| Pine Script: New Library | Opens an untitled document from the library template | +| Pine Script: Generate Docstring | Inserts or completes `//@function`, `//@param`, `//@returns`, `//@type`, `//@field`, `//@enum` for the declaration at the cursor | +| Pine Script: Add Type Annotations | Prefixes untyped declarations with their inferred type in the selection or the whole file | +| Pine Script: Open Reference | Opens the v6 reference at the built-in under the cursor | + +## Settings + +| Setting | Default | Meaning | +| ------------------------------------ | ----------- | ---------------------------------------------------------------------------- | +| `pinescript.completion.enabled` | `true` | Completion provider | +| `pinescript.hover.enabled` | `true` | Hover provider | +| `pinescript.signatureHelp.enabled` | `true` | Parameter hints | +| `pinescript.libraries.local.include` | `**/*.pine` | Glob for workspace library discovery | +| `pinescript.libraries.remote` | `true` | Look up published libraries on TradingView for `import` completion and hover | +| `pinescript.diagnostics.remote` | `false` | Send the document to the TradingView compiler for diagnostics | + +## Privacy + +Everything works offline. Two features talk to TradingView, both through undocumented endpoints that may change: + +- With `pinescript.libraries.remote` on, the prefix you type after `import` and the ids of imported libraries are sent to fetch library metadata and source. +- With `pinescript.diagnostics.remote` on, the **full text** of each open Pine document is sent to the compiler on open, on save and 600 ms after you stop typing. + +Nothing else leaves your machine. When a request fails, the feature is paused for five minutes and the reason is written to the "Pine Script" output channel. + ## Recommended companions - [vscode-icons](https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons) for a `.pine` file icon. -- [One Dark Pro](https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme) for the colors shown in the screenshot. ## How it works -The TextMate grammar in `syntaxes/pinescript.tmLanguage.json` is **generated**. Do not edit it by hand. - ``` src/ - grammar.mjs grammar rules, expressed as data + grammar.mjs grammar rules, expressed as data data/ - functions.json built-in functions, grouped by namespace - variables.json built-in variables, grouped by namespace - constants.json built-in constants, grouped by namespace - annotations.json compiler annotations + functions.json built-in functions, grouped by namespace (grammar source) + variables.json built-in variables, grouped by namespace (grammar source) + constants.json built-in constants, grouped by namespace (grammar source) + annotations.json compiler annotations + reference.json full v6 documentation, generated by scripts/scrape-reference.mjs + extension/ + core/ pure TypeScript: tokenizer, document model, completion context, + type inference, docstrings, templates, TradingView client + providers/ VS Code adapters: completion, hover, signature help, symbols, + code actions, diagnostics, library index + commands/ command implementations scripts/ - build-grammar.mjs compiles src/ into syntaxes/pinescript.tmLanguage.json + build-grammar.mjs compiles src/ into syntaxes/pinescript.tmLanguage.json and + checks it against reference.json + build-extension.mjs bundles src/extension into dist/extension.cjs with esbuild + scrape-reference.mjs regenerates src/data/reference.json (maintainers, needs network) + check-themes.mjs verifies both themes cover every grammar scope +themes/ Pine Dark and Pine Light tests/ - unit/ scope assertions (vscode-tmgrammar-test) - snapshots/ full-file snapshots (vscode-tmgrammar-snap) + core/ vitest suites for src/extension + unit/ scope assertions (vscode-tmgrammar-test) + snapshots/ full-file snapshots (vscode-tmgrammar-snap) ``` -The identifier lists are extracted from the [Pine Script v6 reference](https://www.tradingview.com/pine-script-reference/v6/). When TradingView adds a built-in, add it to the matching JSON file, run the build, and the grammar picks it up. +`syntaxes/pinescript.tmLanguage.json`, `src/data/reference.json` and `dist/` are generated. Do not edit them by hand. ## Development @@ -64,12 +110,13 @@ The identifier lists are extracted from the [Pine Script v6 reference](https://w git clone https://github.com/yankikucuk/pine-script-syntax-highlighter.git cd pine-script-syntax-highlighter npm install -npm run build # regenerate the grammar -npm test # grammar freshness + unit + snapshot tests +npm run build # grammar + extension bundle +npm run watch # rebuild the bundle on change +npm test # grammar and theme checks, type check, core and grammar tests npm run package # build the .vsix ``` -Press F5 in VS Code to launch an Extension Development Host with the extension loaded. Use **Developer: Inspect Editor Tokens and Scopes** from the command palette to see which scope a token receives. +Press F5 in VS Code to launch an Extension Development Host with the extension loaded. Use **Developer: Inspect Editor Tokens and Scopes** to see which scope a token receives. See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the pull request checklist and [CHANGELOG.md](CHANGELOG.md) for release history. diff --git a/docs/plans/2026-09-07-language-features.md b/docs/plans/2026-09-07-language-features.md new file mode 100644 index 0000000..76beb18 --- /dev/null +++ b/docs/plans/2026-09-07-language-features.md @@ -0,0 +1,5407 @@ +# Language Features (3.0.0) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Turn the grammar-only extension into a full Pine Script v6 language extension: completion, hover, signature help, outline, libraries, opt-in compiler diagnostics, docstring and type-annotation commands, templates and two themes, released as 3.0.0. + +**Architecture:** One extension using the VS Code API directly. Pure TypeScript logic in `src/extension/core/` (no `vscode` import, tested with vitest), thin providers and commands on top, bundled with esbuild into `dist/extension.cjs`. Documentation comes from a generated `src/data/reference.json` scraped from the v6 reference; the existing grammar pipeline is untouched. + +**Tech Stack:** TypeScript 5.9, esbuild 0.28, vitest 5, @types/vscode 1.96, playwright-core (optional, scraper only), existing Prettier + vscode-tmgrammar-test. + +**Spec:** `docs/specs/2026-09-07-language-features-design.md` + +## Global Constraints + +- No attribution to tools or assistants anywhere: not in commits, comments, docs, or metadata. Commit messages are plain imperative sentences with no trailers. +- `engines.vscode` is `^1.96.0`; `engines.node` stays `>=20`. No runtime dependencies (`dependencies` stays absent). +- `src/extension/core/**` never imports `vscode`. +- `syntaxes/pinescript.tmLanguage.json` and `src/data/reference.json` are generated; never hand-edited. +- Every remote call goes through `core/pine-facade.ts`; failures are silent to the user and logged to the "Pine Script" output channel. +- `pinescript.diagnostics.remote` defaults to `false`. +- Prettier formats everything it is not told to ignore: run `npm run format` before every commit. +- Commands are prefixed `pinescript.`; settings are prefixed `pinescript.`; command titles start with `Pine Script: `. +- Language id is `pinescript`, scope name `source.pine`. + +--- + +## File map + +| Path | Responsibility | +| ------------------------------------------------------------------------- | ------------------------------------------------------ | +| `tsconfig.json` | strict TS config, `noEmit` | +| `vitest.config.ts` | test discovery for `tests/core` | +| `scripts/build-extension.mjs` | esbuild bundle, `--watch` mode | +| `scripts/scrape-reference.mjs` | headless scrape to `src/data/reference.json` | +| `scripts/lib/extract-reference.js` | browser-side extraction code shared by the scraper | +| `scripts/build-grammar.mjs` | + consistency check against `reference.json` | +| `src/data/reference.json` | generated documentation | +| `src/extension/extension.ts` | `activate`/`deactivate`, registration, settings reload | +| `src/extension/vscode/settings.ts` | typed settings reader | +| `src/extension/vscode/output.ts` | output channel logger | +| `src/extension/vscode/document-cache.ts` | model + tokens cache per document version | +| `src/extension/core/reference.ts` | `ReferenceIndex` | +| `src/extension/core/tokenizer.ts` | `tokenize`, token helpers | +| `src/extension/core/document-model.ts` | `buildModel`, symbols | +| `src/extension/core/call-resolver.ts` | `enclosingCall` | +| `src/extension/core/context.ts` | `completionContext` | +| `src/extension/core/markdown.ts` | documentation strings | +| `src/extension/core/type-inference.ts` | `inferType`, `planTypeAnnotations` | +| `src/extension/core/docstring.ts` | `docstringLines`, `annotationBlockRange` | +| `src/extension/core/templates.ts` | file templates | +| `src/extension/core/pine-facade.ts` | HTTP client with cache and backoff | +| `src/extension/core/diagnostics.ts` | compiler result mapping | +| `src/extension/core/libraries.ts` | `parseLibrary`, library models | +| `src/extension/providers/completion.ts` | CompletionItemProvider | +| `src/extension/providers/hover.ts` | HoverProvider | +| `src/extension/providers/signature-help.ts` | SignatureHelpProvider | +| `src/extension/providers/document-symbol.ts` | DocumentSymbolProvider | +| `src/extension/providers/code-action.ts` | docstring code action | +| `src/extension/providers/diagnostics-controller.ts` | debounced remote diagnostics | +| `src/extension/providers/library-index.ts` | workspace library discovery + remote lookup | +| `src/extension/commands/new-file.ts` | three template commands | +| `src/extension/commands/generate-docstring.ts` | docstring command | +| `src/extension/commands/add-type-annotations.ts` | type annotation command | +| `src/extension/commands/open-reference.ts` | open reference URL | +| `themes/pine-dark-color-theme.json`, `themes/pine-light-color-theme.json` | themes | +| `tests/core/*.test.ts`, `tests/core/fixtures/*.pine` | vitest suites | + +--- + +### Task 1: Toolchain and empty extension + +**Files:** + +- Modify: `package.json` +- Create: `tsconfig.json`, `vitest.config.ts`, `scripts/build-extension.mjs`, `src/extension/extension.ts`, `src/extension/vscode/output.ts`, `tests/core/smoke.test.ts` +- Modify: `.gitignore`, `.vscodeignore`, `.prettierignore`, `.github/workflows/ci.yml` + +**Interfaces:** + +- Produces: `npm run build:extension` → `dist/extension.cjs`; `npm run typecheck`; `npm run test:core`; `log(msg)` from `vscode/output.ts`. + +- [ ] **Step 1: Install dev dependencies** + +```bash +npm install --save-dev typescript@^5.9.3 esbuild@^0.28.2 vitest@^5.0.0 @types/vscode@^1.96.0 @types/node@^22.0.0 +``` + +- [ ] **Step 2: Update `package.json` metadata and scripts** + +Edit these fields (keep everything else): + +```json +"description": "Pine Script v6 for Visual Studio Code: highlighting, completion, hover documentation, signature help, snippets, themes and compiler diagnostics.", +"engines": { "vscode": "^1.96.0", "node": ">=20" }, +"main": "./dist/extension.cjs", +"activationEvents": [], +"categories": ["Programming Languages", "Snippets", "Themes", "Linters"], +"keywords": ["pine", "pinescript", "pine script", "tradingview", "trading", "indicator", "strategy", "syntax", "highlighting", "completion", "hover", "themes"], +"capabilities": { + "untrustedWorkspaces": { "supported": true }, + "virtualWorkspaces": { "supported": "limited", "description": "Workspace library discovery needs files on disk." } +}, +"scripts": { + "build": "npm run build:grammar && npm run build:extension", + "build:grammar": "node scripts/build-grammar.mjs", + "build:grammar:check": "node scripts/build-grammar.mjs --check", + "build:extension": "node scripts/build-extension.mjs", + "watch": "node scripts/build-extension.mjs --watch", + "scrape:reference": "node scripts/scrape-reference.mjs", + "typecheck": "tsc --noEmit", + "test": "npm run build:grammar:check && npm run typecheck && npm run test:core && npm run test:unit && npm run test:snap", + "test:core": "vitest run", + "test:unit": "vscode-tmgrammar-test -g syntaxes/pinescript.tmLanguage.json \"tests/unit/**/*.pine\"", + "test:snap": "vscode-tmgrammar-snap -s source.pine -g syntaxes/pinescript.tmLanguage.json \"tests/snapshots/**/*.pine\"", + "test:snap:update": "npm run test:snap -- --updateSnapshot", + "format": "prettier --write .", + "format:check": "prettier --check .", + "package": "vsce package", + "publish": "vsce publish", + "vscode:prepublish": "npm test && npm run build:extension" +} +``` + +- [ ] **Step 3: Create `tsconfig.json`** + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022"], + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "exactOptionalPropertyTypes": false, + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node", "vscode"] + }, + "include": ["src/extension/**/*.ts", "tests/core/**/*.ts", "vitest.config.ts"] +} +``` + +- [ ] **Step 4: Create `vitest.config.ts`** + +```ts +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/core/**/*.test.ts'], + environment: 'node', + }, +}); +``` + +- [ ] **Step 5: Create `scripts/build-extension.mjs`** + +```js +import { build, context } from 'esbuild'; + +const watch = process.argv.includes('--watch'); + +/** @type {import('esbuild').BuildOptions} */ +const options = { + entryPoints: ['src/extension/extension.ts'], + outfile: 'dist/extension.cjs', + bundle: true, + format: 'cjs', + platform: 'node', + target: 'node20', + external: ['vscode'], + sourcemap: watch ? 'inline' : false, + minify: !watch, + logLevel: 'info', +}; + +if (watch) { + const ctx = await context(options); + await ctx.watch(); +} else { + await build(options); +} +``` + +- [ ] **Step 6: Create `src/extension/vscode/output.ts`** + +```ts +import * as vscode from 'vscode'; + +let channel: vscode.OutputChannel | undefined; + +export function output(): vscode.OutputChannel { + if (!channel) { + channel = vscode.window.createOutputChannel('Pine Script'); + } + return channel; +} + +export function log(message: string): void { + const stamp = new Date().toISOString().slice(11, 19); + output().appendLine(`[${stamp}] ${message}`); +} +``` + +- [ ] **Step 7: Create `src/extension/extension.ts` (minimal)** + +```ts +import * as vscode from 'vscode'; +import { log, output } from './vscode/output'; + +export function activate(context: vscode.ExtensionContext): void { + context.subscriptions.push(output()); + log('Pine Script extension activated'); +} + +export function deactivate(): void {} +``` + +- [ ] **Step 8: Create `tests/core/smoke.test.ts`** + +```ts +import { describe, expect, it } from 'vitest'; + +describe('toolchain', () => { + it('runs tests', () => { + expect(1 + 1).toBe(2); + }); +}); +``` + +- [ ] **Step 9: Ignore files** + +Append to `.gitignore`: + +``` +dist/ +``` + +Replace `.vscodeignore` with: + +``` +.vscode/** +.github/** +.gstack/** +node_modules/** +src/** +scripts/** +tests/** +docs/** +.editorconfig +.gitignore +.prettierrc +.prettierignore +tsconfig.json +vitest.config.ts +package-lock.json +*.vsix +``` + +Append to `.prettierignore`: + +``` +dist/ +src/data/reference.json +``` + +- [ ] **Step 10: Run the toolchain** + +Run: `npm run typecheck && npm run build:extension && npm run test:core` +Expected: no type errors, `dist/extension.cjs` written, 1 test passed. + +- [ ] **Step 11: Update CI** + +In `.github/workflows/ci.yml`, after the `npm run build:grammar:check` step (rename from `build:check`), add: + +```yaml +- run: npm run typecheck +- run: npm run test:core +``` + +and before `npx vsce package` add `- run: npm run build:extension`. After packaging add: + +```yaml +- name: Verify bundle in package + run: unzip -l *.vsix | grep -q 'extension/dist/extension.cjs' +``` + +Update `release.yml` the same way (build extension before `vsce package`). + +- [ ] **Step 12: Format and commit** + +```bash +npm run format +git add -A +git commit -m "Add TypeScript, esbuild and vitest toolchain with an empty extension entry point" +``` + +--- + +### Task 2: Reference scraper and consistency check + +**Files:** + +- Create: `scripts/lib/extract-reference.js`, `scripts/scrape-reference.mjs`, `src/data/reference.json` +- Modify: `scripts/build-grammar.mjs`, `src/data/functions.json`, `src/data/variables.json`, `src/data/constants.json` (reconciliation only) + +**Interfaces:** + +- Produces: `src/data/reference.json` with the schema in spec §4.2 plus `fields: RefParam[]` on every entry (empty for non-types). + +- [ ] **Step 1: Write the browser-side extractor `scripts/lib/extract-reference.js`** + +This file is loaded as text and evaluated in the page. It must be self-contained ES2020, no imports. + +```js +// Runs inside the reference page. Returns { entries: [...] }. +// Kept in its own file so it can be pasted into a browser console for debugging. +async function extractReference() { + const KIND = { + fun: 'function', + var: 'variable', + const: 'constant', + kw: 'keyword', + type: 'type', + an: 'annotation', + op: 'operator', + }; + const BASE = 'https://www.tradingview.com/pine-script-reference/v6/#'; + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + + function clean(text) { + return (text || '') + .replace(/ /g, ' ') + .replace(/[ \t]+\n/g, '\n') + .trim(); + } + + // Converts an element's inline HTML to markdown: → backticks, → link,
→ newline. + function md(el) { + if (!el) return ''; + let out = ''; + for (const node of el.childNodes) { + if (node.nodeType === Node.TEXT_NODE) out += node.textContent; + else if (node.nodeName === 'CODE') out += '`' + node.textContent + '`'; + else if (node.nodeName === 'A') { + const href = node.getAttribute('data-href') || (node.getAttribute('href') || '').replace(/^#/, ''); + out += href ? `[${node.textContent}](${BASE}${href})` : node.textContent; + } else if (node.nodeName === 'BR') out += '\n'; + else out += md(node); + } + return clean(out); + } + + function codeText(pre) { + if (!pre) return ''; + const code = pre.querySelector('code') || pre; + let out = ''; + for (const node of code.childNodes) { + if (node.nodeName === 'BR') out += '\n'; + else out += node.textContent; + } + return clean(out).replace(/ /g, ' '); + } + + // Groups the flat children of an item into sections keyed by sub-header text. + function sections(content) { + const result = { intro: [] }; + let current = 'intro'; + for (const child of content.children) { + if (child.classList.contains('tv-pine-reference-item__header-wrapper')) continue; + if (child.classList.contains('tv-pine-reference-item__sub-header')) { + current = clean(child.textContent).replace(/\s+/g, ' '); + if (!result[current]) result[current] = []; + continue; + } + result[current].push(child); + } + return result; + } + + function parseArgs(nodes) { + const args = []; + for (const node of nodes) { + const typeSpan = node.querySelector('.tv-pine-reference-item__arg-type'); + if (!typeSpan) continue; + const head = clean(typeSpan.textContent); + const m = head.match(/^([\w.]+)\s*\(([^)]*)\)/); + if (!m) continue; + const clone = node.cloneNode(true); + clone.querySelector('.tv-pine-reference-item__arg-type').remove(); + const description = md(clone); + const defMatch = + description.match(/[Dd]efault(?: value)? is ([^.\n]+)/) || description.match(/[Dd]efault:\s*([^.\n]+)/); + args.push({ + name: m[1], + type: m[2].trim(), + description, + optional: /\boptional\b/i.test(description), + default: defMatch ? defMatch[1].trim() : null, + }); + } + return args; + } + + function readOverloadBlock(content) { + const s = sections(content); + const syntaxKey = Object.keys(s).find((k) => k.startsWith('Syntax')); + const syntax = syntaxKey + ? s[syntaxKey].map((n) => codeText(n.matches('pre') ? n : n.querySelector('pre'))).filter(Boolean) + : []; + const returnsNodes = s['Returns'] || []; + const returns = returnsNodes.length ? returnsNodes.map(md).filter(Boolean).join('\n\n') : ''; + return { s, syntax, params: parseArgs(s['Arguments'] || []), returns }; + } + + function returnType(syntax) { + const m = syntax.match(/→\s*(.+)$/); + return m ? m[1].trim() : ''; + } + + const items = [...document.querySelectorAll('.tv-pine-reference-item[id]')]; + const entries = []; + for (const item of items) { + const id = item.id; + const prefix = id.split('_')[0]; + const kind = KIND[prefix]; + if (!kind) continue; + const content = item.querySelector('.tv-pine-reference-item__content'); + const header = clean(item.querySelector('.tv-pine-reference-item__header')?.textContent).replace(/\(\)$/, ''); + const name = header; + const namespace = name.includes('.') ? name.slice(0, name.lastIndexOf('.')) : ''; + + const first = readOverloadBlock(content); + const s = first.s; + const description = (s.intro || []).map(md).filter(Boolean).join('\n\n'); + const remarks = (s['Remarks'] || []).map(md).filter(Boolean).join('\n\n'); + const example = (s['Example'] || []) + .map((n) => codeText(n.matches('pre') ? n : n.querySelector('pre'))) + .filter(Boolean) + .join('\n\n'); + const seeAlso = [...(s['See also'] || []).flatMap((n) => [...n.querySelectorAll('a[data-href]')])].map((a) => + a.getAttribute('data-href'), + ); + const fields = parseArgs(s['Fields'] || []); + const typeNodes = s['Type'] || []; + const type = typeNodes.length ? clean(typeNodes[0].textContent) : null; + + const overloads = []; + if (kind === 'function') { + const anchors = [...content.querySelectorAll('a[data-href^="' + id + '-"]')]; + if (anchors.length <= 1) { + for (const syntax of first.syntax) { + overloads.push({ + syntax, + params: first.params, + returns: { type: returnType(syntax), description: first.returns }, + }); + } + } else { + for (const anchor of anchors) { + anchor.click(); + const wanted = codeText(anchor.querySelector('pre')); + for (let tries = 0; tries < 20; tries++) { + const selected = content.querySelector('pre.tv-pine-reference-item__syntax.selected'); + if (selected && codeText(selected) === wanted) break; + await sleep(25); + } + const block = readOverloadBlock(item.querySelector('.tv-pine-reference-item__content')); + overloads.push({ + syntax: wanted, + params: block.params, + returns: { type: returnType(wanted), description: block.returns }, + }); + } + } + } + + entries.push({ id, kind, name, namespace, description, overloads, fields, type, remarks, example, seeAlso }); + } + entries.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); + return { entries }; +} +``` + +- [ ] **Step 2: Write `scripts/scrape-reference.mjs`** + +```js +// Produces src/data/reference.json from the Pine Script v6 reference. +// Needs playwright-core and a Chromium: `npx playwright-core install chromium` once. +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(here, '..'); +const URL = 'https://www.tradingview.com/pine-script-reference/v6/'; +const OUT = path.join(root, 'src/data/reference.json'); + +let chromium; +try { + ({ chromium } = await import('playwright-core')); +} catch { + console.error( + 'playwright-core is not installed. Run: npm install --no-save playwright-core && npx playwright-core install chromium', + ); + process.exit(1); +} + +const extractor = await readFile(path.join(here, 'lib/extract-reference.js'), 'utf8'); +const browser = await chromium.launch(); +try { + const page = await browser.newPage({ viewport: { width: 1280, height: 720 } }); + await page.goto(URL, { waitUntil: 'networkidle' }); + await page.waitForSelector('.tv-pine-reference-item[id]'); + const { entries } = await page.evaluate(`${extractor}; extractReference();`); + if (entries.length < 900) { + throw new Error(`Only ${entries.length} entries extracted; the page structure may have changed.`); + } + const data = { version: '6', generatedAt: new Date().toISOString().slice(0, 10), entries }; + await writeFile(OUT, JSON.stringify(data, null, 1) + '\n'); + console.log(`Wrote ${entries.length} entries to ${path.relative(root, OUT)}`); +} finally { + await browser.close(); +} +``` + +- [ ] **Step 3: Run the scraper** + +```bash +npm install --no-save playwright-core@^1.63.0 +npx playwright-core install chromium +npm run scrape:reference +``` + +Expected: `Wrote 94x entries to src/data/reference.json`. Spot check: + +```bash +node -e 'const d=require("./src/data/reference.json"); const f=d.entries.find(e=>e.id==="fun_str.tostring"); console.log(f.overloads.length, f.overloads[1].params.map(p=>p.name+":"+p.type)); const c=d.entries.find(e=>e.id==="var_close"); console.log(c.type); const t=d.entries.find(e=>e.id==="type_chart.point"); console.log(t.fields.map(f=>f.name));' +``` + +Expected: `5 [ 'value:simple int/float', 'format:simple string' ]`, `series float`, `[ 'index', 'time', 'price' ]`. + +If the scrape leaves `playwright-core` in `package-lock.json`, run `npm install` again to drop it (it was installed with `--no-save`). + +- [ ] **Step 4: Add the consistency check to `scripts/build-grammar.mjs`** + +Add a function and call it in both normal and `--check` mode, before writing: + +```js +import reference from '../src/data/reference.json' with { type: 'json' }; +import functions from '../src/data/functions.json' with { type: 'json' }; +import variables from '../src/data/variables.json' with { type: 'json' }; +import constants from '../src/data/constants.json' with { type: 'json' }; + +function flatten(groups) { + const names = new Set(); + for (const [ns, members] of Object.entries(groups)) { + for (const m of members) names.add(ns ? `${ns}.${m}` : m); + } + return names; +} + +function checkReferenceConsistency() { + const byKind = { function: new Set(), variable: new Set(), constant: new Set() }; + for (const e of reference.entries) byKind[e.kind]?.add(e.name); + const grammar = { function: flatten(functions), variable: flatten(variables), constant: flatten(constants) }; + const problems = []; + for (const kind of Object.keys(byKind)) { + for (const n of grammar[kind]) + if (!byKind[kind].has(n)) problems.push(`${kind} "${n}" is in grammar data but not in reference.json`); + for (const n of byKind[kind]) + if (!grammar[kind].has(n)) problems.push(`${kind} "${n}" is in reference.json but not in grammar data`); + } + if (problems.length) { + console.error(problems.join('\n')); + throw new Error(`${problems.length} reference/grammar mismatches`); + } +} +``` + +(If the existing file already imports JSON differently, follow its style.) + +- [ ] **Step 5: Reconcile grammar data** + +Run: `npm run build:grammar:check` +Expected: a list of mismatches (the reference has 475 functions and 239 constants against 471 and 237 in grammar data). For each name reported as missing from grammar data, add it to the matching namespace array in `src/data/functions.json` / `variables.json` / `constants.json`. For each name reported as missing from the reference, remove it from grammar data. Then: + +```bash +npm run build:grammar && npm run build:grammar:check && npm run test:unit && npm run test:snap +``` + +Expected: build passes, all grammar tests pass (update snapshots with `npm run test:snap:update` only if a snapshot file uses a renamed built-in; inspect the diff first). + +- [ ] **Step 6: Commit** + +```bash +npm run format +git add -A +git commit -m "Scrape the v6 reference into reference.json and keep grammar data consistent with it" +``` + +--- + +### Task 3: Reference index + +**Files:** + +- Create: `src/extension/core/reference.ts`, `tests/core/reference.test.ts` + +**Interfaces:** + +- Produces: + + ```ts + type EntryKind = 'function' | 'variable' | 'constant' | 'keyword' | 'type' | 'annotation' | 'operator'; + interface RefParam { + name: string; + type: string; + description: string; + optional: boolean; + default: string | null; + } + interface RefOverload { + syntax: string; + params: RefParam[]; + returns: { type: string; description: string } | null; + } + interface RefEntry { + id: string; + kind: EntryKind; + name: string; + namespace: string; + description: string; + overloads: RefOverload[]; + fields: RefParam[]; + type: string | null; + remarks: string; + example: string; + seeAlso: string[]; + } + class ReferenceIndex { + constructor(data: ReferenceData); + get(name: string, kind?: EntryKind): RefEntry | undefined; // kind priority: function, variable, constant, type, keyword, annotation, operator + getAll(name: string): RefEntry[]; + members(namespace: string): RefEntry[]; // direct members, sorted by name + childNamespaces(namespace: string): string[]; // e.g. 'chart' -> ['point']; '' -> top-level namespaces + bare(): RefEntry[]; // namespace === '' and kind in function|variable|constant + byKind(kind: EntryKind): RefEntry[]; + url(entry: RefEntry): string; + static baseType(qualified: string): string; // 'series float' -> 'float', 'series int/float' -> 'float' + } + function loadReference(): ReferenceIndex; // lazy singleton over the bundled JSON + ``` + +- [ ] **Step 1: Write the failing test `tests/core/reference.test.ts`** + +```ts +import { describe, expect, it } from 'vitest'; +import { ReferenceIndex, loadReference } from '../../src/extension/core/reference'; + +describe('ReferenceIndex', () => { + const ref = loadReference(); + + it('finds a function with its overloads', () => { + const e = ref.get('ta.sma'); + expect(e?.kind).toBe('function'); + expect(e?.overloads[0]?.params.map((p) => p.name)).toEqual(['source', 'length']); + expect(e?.overloads[0]?.returns?.type).toBe('series float'); + }); + + it('prefers the function when a name is both function and variable', () => { + expect(ref.get('time')?.kind).toBe('function'); + expect(ref.get('time', 'variable')?.kind).toBe('variable'); + expect( + ref + .getAll('time') + .map((e) => e.kind) + .sort(), + ).toEqual(['function', 'variable']); + }); + + it('lists namespace members and child namespaces', () => { + expect(ref.members('ta').some((e) => e.name === 'ta.ema')).toBe(true); + expect(ref.members('ta').every((e) => e.namespace === 'ta')).toBe(true); + expect(ref.childNamespaces('chart')).toContain('point'); + expect(ref.childNamespaces('')).toContain('ta'); + expect(ref.childNamespaces('')).not.toContain(''); + }); + + it('exposes bare built-ins and urls', () => { + expect(ref.bare().some((e) => e.name === 'close')).toBe(true); + expect(ref.url(ref.get('close')!)).toBe('https://www.tradingview.com/pine-script-reference/v6/#var_close'); + }); + + it('strips qualifiers from types', () => { + expect(ReferenceIndex.baseType('series float')).toBe('float'); + expect(ReferenceIndex.baseType('series int/float')).toBe('float'); + expect(ReferenceIndex.baseType('const string')).toBe('string'); + expect(ReferenceIndex.baseType('array')).toBe('array'); + }); +}); +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `npx vitest run tests/core/reference.test.ts` +Expected: FAIL, module not found. + +- [ ] **Step 3: Implement `src/extension/core/reference.ts`** + +```ts +import data from '../../data/reference.json'; + +export type EntryKind = 'function' | 'variable' | 'constant' | 'keyword' | 'type' | 'annotation' | 'operator'; + +export interface RefParam { + name: string; + type: string; + description: string; + optional: boolean; + default: string | null; +} + +export interface RefOverload { + syntax: string; + params: RefParam[]; + returns: { type: string; description: string } | null; +} + +export interface RefEntry { + id: string; + kind: EntryKind; + name: string; + namespace: string; + description: string; + overloads: RefOverload[]; + fields: RefParam[]; + type: string | null; + remarks: string; + example: string; + seeAlso: string[]; +} + +export interface ReferenceData { + version: string; + generatedAt: string; + entries: RefEntry[]; +} + +export const REFERENCE_URL = 'https://www.tradingview.com/pine-script-reference/v6/'; + +const KIND_PRIORITY: EntryKind[] = ['function', 'variable', 'constant', 'type', 'keyword', 'annotation', 'operator']; +const QUALIFIERS = /^(?:series|simple|const|input|literal)\s+/; + +export class ReferenceIndex { + private readonly byName = new Map(); + private readonly byNamespace = new Map(); + private readonly children = new Map>(); + private readonly kinds = new Map(); + + constructor(public readonly data: ReferenceData) { + for (const entry of data.entries) { + push(this.byName, entry.name, entry); + push(this.byNamespace, entry.namespace, entry); + push(this.kinds, entry.kind, entry); + // Register every namespace segment as a child of its parent: 'a.b.c' -> ''->a, a->b, a.b->c + const parts = entry.name.split('.'); + for (let i = 1; i < parts.length; i++) { + const parent = parts.slice(0, i - 1).join('.'); + const child = parts[i - 1]!; + if (!this.children.has(parent)) this.children.set(parent, new Set()); + this.children.get(parent)!.add(child); + } + } + for (const list of this.byNamespace.values()) list.sort((a, b) => a.name.localeCompare(b.name)); + } + + get(name: string, kind?: EntryKind): RefEntry | undefined { + const all = this.byName.get(name); + if (!all) return undefined; + if (kind) return all.find((e) => e.kind === kind); + for (const k of KIND_PRIORITY) { + const hit = all.find((e) => e.kind === k); + if (hit) return hit; + } + return all[0]; + } + + getAll(name: string): RefEntry[] { + return this.byName.get(name) ?? []; + } + + members(namespace: string): RefEntry[] { + return this.byNamespace.get(namespace) ?? []; + } + + childNamespaces(namespace: string): string[] { + return [...(this.children.get(namespace) ?? [])].sort(); + } + + bare(): RefEntry[] { + return this.members('').filter((e) => e.kind === 'function' || e.kind === 'variable' || e.kind === 'constant'); + } + + byKind(kind: EntryKind): RefEntry[] { + return this.kinds.get(kind) ?? []; + } + + url(entry: RefEntry): string { + return `${REFERENCE_URL}#${entry.id}`; + } + + static baseType(qualified: string): string { + const stripped = qualified.trim().replace(QUALIFIERS, ''); + // 'int/float' means either; the wider type is float. + if (stripped === 'int/float') return 'float'; + return stripped; + } +} + +function push(map: Map, key: K, value: V): void { + const list = map.get(key); + if (list) list.push(value); + else map.set(key, [value]); +} + +let singleton: ReferenceIndex | undefined; + +export function loadReference(): ReferenceIndex { + if (!singleton) singleton = new ReferenceIndex(data as ReferenceData); + return singleton; +} +``` + +- [ ] **Step 4: Run to verify it passes** + +Run: `npx vitest run tests/core/reference.test.ts` +Expected: PASS (5 tests). If `time` is not both a function and a variable in the scraped data, pick another dual name from `reference.json` (`node -e` over `entries` grouped by name) and adjust the test. + +- [ ] **Step 5: Commit** + +```bash +npm run format +git add src/extension/core/reference.ts tests/core/reference.test.ts +git commit -m "Add reference index over the scraped v6 documentation" +``` + +--- + +### Task 4: Tokenizer + +**Files:** + +- Create: `src/extension/core/tokenizer.ts`, `tests/core/tokenizer.test.ts` + +**Interfaces:** + +- Produces: + + ```ts + type TokenKind = 'comment' | 'string' | 'number' | 'ident' | 'op' | 'open' | 'close' | 'comma' | 'ws'; + interface Token { + kind: TokenKind; + start: number; + end: number; + text: string; + line: number; + } + interface TokenizedLine { + tokens: Token[]; + depthAtStart: number; + continuesString: boolean; + } + function tokenize(text: string): TokenizedLine[]; + function tokenAt(tokens: Token[], col: number): Token | undefined; // token with start <= col < end, else the token ending exactly at col + function isInStringOrComment(line: TokenizedLine, col: number): boolean; + function wordAt(lineText: string, col: number): { text: string; start: number; end: number } | null; // dotted identifier around col + ``` + +- [ ] **Step 1: Write the failing test `tests/core/tokenizer.test.ts`** + +```ts +import { describe, expect, it } from 'vitest'; +import { isInStringOrComment, tokenAt, tokenize, wordAt } from '../../src/extension/core/tokenizer'; + +const kinds = (line: string) => + tokenize(line)[0]! + .tokens.filter((t) => t.kind !== 'ws') + .map((t) => `${t.kind}:${t.text}`); + +describe('tokenize', () => { + it('splits a plot call', () => { + expect(kinds('plot(ta.sma(close, 14), color = #ff0000aa)')).toEqual([ + 'ident:plot', + 'open:(', + 'ident:ta.sma', + 'open:(', + 'ident:close', + 'comma:,', + 'number:14', + 'close:)', + 'comma:,', + 'ident:color', + 'op:=', + 'number:#ff0000aa', + 'close:)', + ]); + }); + + it('keeps strings and comments whole', () => { + expect(kinds('x = "a // not comment" // real')).toEqual([ + 'ident:x', + 'op:=', + 'string:"a // not comment"', + 'comment:// real', + ]); + expect(kinds("s = 'it\\'s'")).toEqual(['ident:s', 'op:=', "string:'it\\'s'"]); + }); + + it('carries triple-quoted strings across lines', () => { + const lines = tokenize('t = """first\nsecond""" + x'); + expect(lines[0]!.tokens.at(-1)?.kind).toBe('string'); + expect(lines[0]!.continuesString).toBe(true); + expect(lines[1]!.tokens.map((t) => t.kind)).toEqual(['string', 'ws', 'op', 'ws', 'ident']); + }); + + it('tracks bracket depth across lines', () => { + const lines = tokenize('f(a,\n b)\nc = 1'); + expect(lines.map((l) => l.depthAtStart)).toEqual([0, 1, 0]); + }); + + it('recognizes multi-character operators', () => { + expect(kinds('a := b == c ? d => e')).toEqual([ + 'ident:a', + 'op::=', + 'ident:b', + 'op:==', + 'ident:c', + 'op:?', + 'ident:d', + 'op:=>', + 'ident:e', + ]); + }); + + it('tells strings and comments apart from code', () => { + const line = tokenize('x = "abc" // c')[0]!; + expect(isInStringOrComment(line, 0)).toBe(false); + expect(isInStringOrComment(line, 6)).toBe(true); + expect(isInStringOrComment(line, 12)).toBe(true); + }); + + it('finds tokens and words at a column', () => { + const line = tokenize('ta.sma(close)')[0]!; + expect(tokenAt(line.tokens, 3)?.text).toBe('ta.sma'); + expect(tokenAt(line.tokens, 6)?.text).toBe('ta.sma'); + expect(wordAt('ta.sma(close)', 4)).toEqual({ text: 'ta.sma', start: 0, end: 6 }); + expect(wordAt('ta.sma(close)', 9)).toEqual({ text: 'close', start: 7, end: 12 }); + expect(wordAt('ta.sma(close)', 6)).toEqual({ text: 'ta.sma', start: 0, end: 6 }); + }); +}); +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `npx vitest run tests/core/tokenizer.test.ts` +Expected: FAIL, module not found. + +- [ ] **Step 3: Implement `src/extension/core/tokenizer.ts`** + +```ts +export type TokenKind = 'comment' | 'string' | 'number' | 'ident' | 'op' | 'open' | 'close' | 'comma' | 'ws'; + +export interface Token { + kind: TokenKind; + start: number; + end: number; + text: string; + line: number; +} + +export interface TokenizedLine { + tokens: Token[]; + depthAtStart: number; + continuesString: boolean; +} + +const IDENT = /[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/y; +const NUMBER = /#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?|(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?/y; +const OPS = [ + '=>', + ':=', + '==', + '!=', + '<=', + '>=', + '+=', + '-=', + '*=', + '/=', + '%=', + '?', + ':', + '+', + '-', + '*', + '/', + '%', + '=', + '<', + '>', + '!', +]; + +export function tokenize(text: string): TokenizedLine[] { + const lines = text.split(/\r?\n/); + const result: TokenizedLine[] = []; + let depth = 0; + let openString: string | null = null; // '"""' or "'''" while inside a triple-quoted string + + for (let lineNo = 0; lineNo < lines.length; lineNo++) { + const line = lines[lineNo]!; + const tokens: Token[] = []; + const depthAtStart = depth; + let i = 0; + + const push = (kind: TokenKind, start: number, end: number) => { + tokens.push({ kind, start, end, text: line.slice(start, end), line: lineNo }); + }; + + if (openString) { + const close = line.indexOf(openString); + if (close === -1) { + push('string', 0, line.length); + result.push({ tokens, depthAtStart, continuesString: true }); + continue; + } + push('string', 0, close + 3); + i = close + 3; + openString = null; + } + + while (i < line.length) { + const ch = line[i]!; + if (ch === ' ' || ch === '\t') { + let j = i + 1; + while (j < line.length && (line[j] === ' ' || line[j] === '\t')) j++; + push('ws', i, j); + i = j; + continue; + } + if (ch === '/' && line[i + 1] === '/') { + push('comment', i, line.length); + i = line.length; + continue; + } + if (ch === '"' || ch === "'") { + const triple = line.startsWith(ch.repeat(3), i); + if (triple) { + const close = line.indexOf(ch.repeat(3), i + 3); + if (close === -1) { + push('string', i, line.length); + openString = ch.repeat(3); + i = line.length; + continue; + } + push('string', i, close + 3); + i = close + 3; + continue; + } + let j = i + 1; + while (j < line.length && line[j] !== ch) { + if (line[j] === '\\') j++; + j++; + } + push('string', i, Math.min(j + 1, line.length)); + i = Math.min(j + 1, line.length); + continue; + } + IDENT.lastIndex = i; + const id = IDENT.exec(line); + if (id) { + push('ident', i, i + id[0].length); + i += id[0].length; + continue; + } + NUMBER.lastIndex = i; + const num = NUMBER.exec(line); + if (num && (ch === '#' || /\d/.test(ch) || (ch === '.' && /\d/.test(line[i + 1] ?? '')))) { + push('number', i, i + num[0].length); + i += num[0].length; + continue; + } + if (ch === '(' || ch === '[') { + depth++; + push('open', i, i + 1); + i++; + continue; + } + if (ch === ')' || ch === ']') { + depth = Math.max(0, depth - 1); + push('close', i, i + 1); + i++; + continue; + } + if (ch === ',') { + push('comma', i, i + 1); + i++; + continue; + } + const op = OPS.find((o) => line.startsWith(o, i)); + if (op) { + push('op', i, i + op.length); + i += op.length; + continue; + } + push('op', i, i + 1); + i++; + } + result.push({ tokens, depthAtStart, continuesString: openString !== null }); + } + return result; +} + +export function tokenAt(tokens: Token[], col: number): Token | undefined { + return tokens.find((t) => t.start <= col && col < t.end) ?? tokens.find((t) => t.end === col && t.kind === 'ident'); +} + +export function isInStringOrComment(line: TokenizedLine, col: number): boolean { + const t = + line.tokens.find((t) => t.start <= col && col < t.end) ?? line.tokens.find((t) => t.start < col && col <= t.end); + if (!t) return line.continuesString && line.tokens.length === 0; + return t.kind === 'string' || t.kind === 'comment'; +} + +export function wordAt(lineText: string, col: number): { text: string; start: number; end: number } | null { + const isWord = (c: string) => /[A-Za-z0-9_.]/.test(c); + let start = col; + let end = col; + while (start > 0 && isWord(lineText[start - 1]!)) start--; + while (end < lineText.length && isWord(lineText[end]!)) end++; + if (start === end) return null; + const text = lineText.slice(start, end).replace(/^\.+|\.+$/g, ''); + if (!text) return null; + const s = lineText.indexOf(text, start); + return { text, start: s, end: s + text.length }; +} +``` + +- [ ] **Step 4: Run to verify it passes** + +Run: `npx vitest run tests/core/tokenizer.test.ts` +Expected: PASS (7 tests). + +- [ ] **Step 5: Commit** + +```bash +npm run format +git add src/extension/core/tokenizer.ts tests/core/tokenizer.test.ts +git commit -m "Add line tokenizer for Pine Script" +``` + +--- + +### Task 5: Document model + +**Files:** + +- Create: `src/extension/core/document-model.ts`, `tests/core/document-model.test.ts`, `tests/core/fixtures/library.pine`, `tests/core/fixtures/consumer.pine` + +**Interfaces:** + +- Consumes: `tokenize` from Task 4. +- Produces: + + ```ts + interface LineRange { + start: number; + end: number; + } // inclusive line numbers + interface Annotations { + function: string | null; + description: string | null; + params: Record; + returns: string | null; + type: string | null; + fields: Record; + enum: string | null; + raw: string[]; + } + interface ParamDecl { + name: string; + type: string | null; + default: string | null; + } + interface FunctionSymbol { + kind: 'function'; + name: string; + isMethod: boolean; + isExport: boolean; + params: ParamDecl[]; + docs: Annotations; + line: number; + range: LineRange; + } + interface FieldDecl { + name: string; + type: string; + default: string | null; + } + interface TypeSymbol { + kind: 'type'; + name: string; + isExport: boolean; + fields: FieldDecl[]; + docs: Annotations; + line: number; + range: LineRange; + } + interface EnumMember { + name: string; + title: string | null; + } + interface EnumSymbol { + kind: 'enum'; + name: string; + isExport: boolean; + members: EnumMember[]; + docs: Annotations; + line: number; + range: LineRange; + } + interface VariableSymbol { + kind: 'variable'; + name: string; + declaredType: string | null; + qualifier: 'var' | 'varip' | null; + initializer: string | null; + line: number; + column: number; + scope: LineRange; + } + interface ImportDecl { + owner: string; + name: string; + version: string; + alias: string | null; + line: number; + } + interface DocumentModel { + version: number | null; + scriptKind: 'indicator' | 'strategy' | 'library' | null; + libraryTitle: string | null; + imports: ImportDecl[]; + functions: FunctionSymbol[]; + types: TypeSymbol[]; + enums: EnumSymbol[]; + variables: VariableSymbol[]; + lineCount: number; + } + type DeclSymbol = FunctionSymbol | TypeSymbol | EnumSymbol; + function buildModel(text: string): DocumentModel; + function declarationAt(model: DocumentModel, line: number): DeclSymbol | null; // header line only + function visibleVariables(model: DocumentModel, line: number): VariableSymbol[]; + function emptyAnnotations(): Annotations; + ``` + +- [ ] **Step 1: Write fixtures** + +`tests/core/fixtures/library.pine`: + +```pine +//@version=6 +//@description Helpers for moving averages. +library("MaHelpers", overlay = true) + +//@function Weighted average of two series. +//@param a First series. +//@param b Second series. +//@param w Weight of `a`, between 0 and 1. +//@returns The weighted average. +export weighted(float a, float b, + float w = 0.5) => + a * w + b * (1 - w) + +//@type A price level with a label. +//@field price The level. +//@field name Display name. +export type Level + float price + string name = "level" + +//@enum Trade direction. +export enum Side + long = "Long" + short + +method describe(Level this) => + str.format("{0}: {1}", this.name, this.price) + +internal(x) => + y = x * 2 + y +``` + +`tests/core/fixtures/consumer.pine`: + +```pine +//@version=6 +indicator("Consumer", shorttitle = "C", overlay = false) +import yankikucuk/MaHelpers/2 as ma +import TradingView/ta/14 + +length = input.int(14, "Length") +var float acc = na +src = close +[dc, up] = ta.dmi(14, 14) +fast = ta.ema(src, length) +plot(fast, title = "Fast", + color = color.new(color.blue, 0)) +if fast > src + label.new(bar_index, high, "up") +``` + +- [ ] **Step 2: Write the failing test `tests/core/document-model.test.ts`** + +```ts +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; +import { buildModel, declarationAt, visibleVariables } from '../../src/extension/core/document-model'; + +const fixture = (name: string) => readFileSync(new URL(`./fixtures/${name}`, import.meta.url), 'utf8'); + +describe('buildModel on a library', () => { + const m = buildModel(fixture('library.pine')); + + it('reads the header', () => { + expect(m.version).toBe(6); + expect(m.scriptKind).toBe('library'); + expect(m.libraryTitle).toBe('MaHelpers'); + }); + + it('parses exported functions with multi-line params and docs', () => { + const f = m.functions.find((f) => f.name === 'weighted')!; + expect(f.isExport).toBe(true); + expect(f.isMethod).toBe(false); + expect(f.params).toEqual([ + { name: 'a', type: 'float', default: null }, + { name: 'b', type: 'float', default: null }, + { name: 'w', type: 'float', default: '0.5' }, + ]); + expect(f.docs.function).toBe('Weighted average of two series.'); + expect(f.docs.params.w).toBe('Weight of `a`, between 0 and 1.'); + expect(f.docs.returns).toBe('The weighted average.'); + expect(f.range).toEqual({ start: 9, end: 11 }); + }); + + it('parses types and enums', () => { + const t = m.types.find((t) => t.name === 'Level')!; + expect(t.fields).toEqual([ + { name: 'price', type: 'float', default: null }, + { name: 'name', type: 'string', default: '"level"' }, + ]); + expect(t.docs.fields.price).toBe('The level.'); + const e = m.enums.find((e) => e.name === 'Side')!; + expect(e.members).toEqual([ + { name: 'long', title: '"Long"' }, + { name: 'short', title: null }, + ]); + expect(e.docs.enum).toBe('Trade direction.'); + }); + + it('parses methods and private functions', () => { + const d = m.functions.find((f) => f.name === 'describe')!; + expect(d.isMethod).toBe(true); + expect(d.params[0]).toEqual({ name: 'this', type: 'Level', default: null }); + const i = m.functions.find((f) => f.name === 'internal')!; + expect(i.isExport).toBe(false); + expect(i.params).toEqual([{ name: 'x', type: null, default: null }]); + }); + + it('scopes local variables to their function', () => { + const y = m.variables.find((v) => v.name === 'y')!; + expect(y.scope).toEqual({ start: 28, end: 29 }); + expect(visibleVariables(m, 29).map((v) => v.name)).toContain('y'); + expect(visibleVariables(m, 3).map((v) => v.name)).not.toContain('y'); + }); + + it('finds declarations by header line', () => { + expect(declarationAt(m, 9)?.name).toBe('weighted'); + expect(declarationAt(m, 10)).toBeNull(); + expect(declarationAt(m, 17)?.kind).toBe('type'); + }); +}); + +describe('buildModel on a consumer script', () => { + const m = buildModel(fixture('consumer.pine')); + + it('reads imports', () => { + expect(m.scriptKind).toBe('indicator'); + expect(m.imports).toEqual([ + { owner: 'yankikucuk', name: 'MaHelpers', version: '2', alias: 'ma', line: 2 }, + { owner: 'TradingView', name: 'ta', version: '14', alias: null, line: 3 }, + ]); + }); + + it('reads variable declarations with types, qualifiers and tuples', () => { + const byName = Object.fromEntries(m.variables.map((v) => [v.name, v])); + expect(byName.length).toMatchObject({ + declaredType: null, + qualifier: null, + initializer: 'input.int(14, "Length")', + }); + expect(byName.acc).toMatchObject({ declaredType: 'float', qualifier: 'var', initializer: 'na' }); + expect(byName.dc).toMatchObject({ initializer: null }); + expect(byName.up).toMatchObject({ initializer: null }); + expect(byName.fast?.initializer).toBe('ta.ema(src, length)'); + }); + + it('does not treat named arguments on continuation lines as variables', () => { + expect(m.variables.some((v) => v.name === 'color')).toBe(false); + }); +}); +``` + +- [ ] **Step 3: Run to verify it fails** + +Run: `npx vitest run tests/core/document-model.test.ts` +Expected: FAIL, module not found. + +- [ ] **Step 4: Implement `src/extension/core/document-model.ts`** + +```ts +import { tokenize, type TokenizedLine } from './tokenizer'; + +export interface LineRange { + start: number; + end: number; +} + +export interface Annotations { + function: string | null; + description: string | null; + params: Record; + returns: string | null; + type: string | null; + fields: Record; + enum: string | null; + raw: string[]; +} + +export interface ParamDecl { + name: string; + type: string | null; + default: string | null; +} + +export interface FunctionSymbol { + kind: 'function'; + name: string; + isMethod: boolean; + isExport: boolean; + params: ParamDecl[]; + docs: Annotations; + line: number; + range: LineRange; +} + +export interface FieldDecl { + name: string; + type: string; + default: string | null; +} + +export interface TypeSymbol { + kind: 'type'; + name: string; + isExport: boolean; + fields: FieldDecl[]; + docs: Annotations; + line: number; + range: LineRange; +} + +export interface EnumMember { + name: string; + title: string | null; +} + +export interface EnumSymbol { + kind: 'enum'; + name: string; + isExport: boolean; + members: EnumMember[]; + docs: Annotations; + line: number; + range: LineRange; +} + +export interface VariableSymbol { + kind: 'variable'; + name: string; + declaredType: string | null; + qualifier: 'var' | 'varip' | null; + initializer: string | null; + line: number; + column: number; + scope: LineRange; +} + +export interface ImportDecl { + owner: string; + name: string; + version: string; + alias: string | null; + line: number; +} + +export interface DocumentModel { + version: number | null; + scriptKind: 'indicator' | 'strategy' | 'library' | null; + libraryTitle: string | null; + imports: ImportDecl[]; + functions: FunctionSymbol[]; + types: TypeSymbol[]; + enums: EnumSymbol[]; + variables: VariableSymbol[]; + lineCount: number; +} + +export type DeclSymbol = FunctionSymbol | TypeSymbol | EnumSymbol; + +const TYPE = String.raw`[A-Za-z_][\w.]*(?:<[^<>]*(?:<[^<>]*>[^<>]*)*>)?(?:\[\])?`; +const NAME = String.raw`[A-Za-z_]\w*`; +const RE_VERSION = /^\/\/@version\s*=\s*(\d+)/; +const RE_DECLARATION = /^(indicator|strategy|library)\s*\(\s*(?:title\s*=\s*)?("([^"]*)"|'([^']*)')?/; +const RE_IMPORT = /^import\s+([\w-]+)\/([\w-]+)\/(\d+)(?:\s+as\s+(\w+))?/; +const RE_FUNCTION_HEAD = new RegExp(String.raw`^(export\s+)?(method\s+)?(${NAME})\s*\(`); +const RE_TYPE = new RegExp(String.raw`^(export\s+)?type\s+(${NAME})\s*$`); +const RE_ENUM = new RegExp(String.raw`^(export\s+)?enum\s+(${NAME})\s*$`); +const RE_FIELD = new RegExp(String.raw`^(${TYPE})\s+(${NAME})(?:\s*=\s*(.+))?$`); +const RE_ENUM_MEMBER = new RegExp(String.raw`^(${NAME})(?:\s*=\s*(.+))?$`); +const RE_VARIABLE = new RegExp(String.raw`^(?:(var|varip)\s+)?(?:(${TYPE})\s+)?(${NAME})\s*=(?![=>])\s*(.*)$`); +const RE_TUPLE = /^\[\s*([\w\s,]+)\]\s*=(?![=>])/; +const RE_PARAM = new RegExp(String.raw`^(?:(${TYPE})\s+)?(${NAME})(?:\s*=\s*(.+))?$`); + +export function emptyAnnotations(): Annotations { + return { function: null, description: null, params: {}, returns: null, type: null, fields: {}, enum: null, raw: [] }; +} + +export function buildModel(text: string): DocumentModel { + const lines = text.split(/\r?\n/); + const tokenLines = tokenize(text); + const model: DocumentModel = { + version: null, + scriptKind: null, + libraryTitle: null, + imports: [], + functions: [], + types: [], + enums: [], + variables: [], + lineCount: lines.length, + }; + + for (let i = 0; i < lines.length; i++) { + const raw = lines[i]!; + const tl = tokenLines[i]!; + if (tl.depthAtStart > 0 || tl.tokens[0]?.kind === 'string') continue; // continuation line + const indent = raw.length - raw.trimStart().length; + const code = stripComment(raw, tl).trim(); + if (!code) { + const v = raw.match(RE_VERSION); + if (v) model.version = Number(v[1]); + continue; + } + + if (indent === 0) { + const decl = code.match(RE_DECLARATION); + if (decl) { + model.scriptKind = decl[1] as DocumentModel['scriptKind']; + if (decl[1] === 'library') model.libraryTitle = decl[3] ?? decl[4] ?? null; + } + const imp = code.match(RE_IMPORT); + if (imp) { + model.imports.push({ owner: imp[1]!, name: imp[2]!, version: imp[3]!, alias: imp[4] ?? null, line: i }); + continue; + } + } + + const typeMatch = code.match(RE_TYPE); + if (typeMatch) { + const range = blockRange(lines, i, indent); + const fields: FieldDecl[] = []; + for (let j = i + 1; j <= range.end; j++) { + const f = stripComment(lines[j]!, tokenLines[j]!).trim().match(RE_FIELD); + if (f) fields.push({ name: f[2]!, type: f[1]!, default: f[3]?.trim() ?? null }); + } + model.types.push({ + kind: 'type', + name: typeMatch[2]!, + isExport: !!typeMatch[1], + fields, + docs: annotationsAbove(lines, i), + line: i, + range, + }); + i = range.end; + continue; + } + + const enumMatch = code.match(RE_ENUM); + if (enumMatch) { + const range = blockRange(lines, i, indent); + const members: EnumMember[] = []; + for (let j = i + 1; j <= range.end; j++) { + const m = stripComment(lines[j]!, tokenLines[j]!).trim().match(RE_ENUM_MEMBER); + if (m) members.push({ name: m[1]!, title: m[2]?.trim() ?? null }); + } + model.enums.push({ + kind: 'enum', + name: enumMatch[2]!, + isExport: !!enumMatch[1], + members, + docs: annotationsAbove(lines, i), + line: i, + range, + }); + i = range.end; + continue; + } + + const fn = code.match(RE_FUNCTION_HEAD); + if (fn) { + const header = joinHeader(lines, tokenLines, i); + if (header && /=>\s*$/.test(header.text)) { + const range = blockRange(lines, header.endLine, indent); + range.start = i; + const paramText = header.text.slice(header.text.indexOf('(') + 1, header.text.lastIndexOf(')')); + model.functions.push({ + kind: 'function', + name: fn[3]!, + isMethod: !!fn[2], + isExport: !!fn[1], + params: parseParams(paramText), + docs: annotationsAbove(lines, i), + line: i, + range, + }); + // Body variables are collected in the main loop; the header lines are skipped here. + i = header.endLine; + continue; + } + } + + const tuple = code.match(RE_TUPLE); + if (tuple) { + for (const name of tuple[1]! + .split(',') + .map((s) => s.trim()) + .filter(Boolean)) { + model.variables.push({ + kind: 'variable', + name, + declaredType: null, + qualifier: null, + initializer: null, + line: i, + column: raw.indexOf(name), + scope: { start: i, end: lines.length - 1 }, + }); + } + continue; + } + + const v = code.match(RE_VARIABLE); + if (v && !isKeyword(v[3]!)) { + model.variables.push({ + kind: 'variable', + name: v[3]!, + declaredType: v[2] ?? null, + qualifier: (v[1] as 'var' | 'varip' | undefined) ?? null, + initializer: v[4]?.trim() || null, + line: i, + column: raw.indexOf(v[3]!), + scope: { start: i, end: lines.length - 1 }, + }); + } + } + + // Narrow variable scopes to the enclosing function body. + for (const variable of model.variables) { + const owner = model.functions.find((f) => variable.line > f.line && variable.line <= f.range.end); + if (owner) variable.scope = { start: variable.line, end: owner.range.end }; + } + return model; +} + +export function declarationAt(model: DocumentModel, line: number): DeclSymbol | null { + return ( + model.functions.find((f) => f.line === line) ?? + model.types.find((t) => t.line === line) ?? + model.enums.find((e) => e.line === line) ?? + null + ); +} + +export function visibleVariables(model: DocumentModel, line: number): VariableSymbol[] { + return model.variables.filter((v) => v.scope.start <= line && line <= v.scope.end); +} + +const KEYWORDS = new Set([ + 'if', + 'else', + 'for', + 'while', + 'switch', + 'once', + 'return', + 'and', + 'or', + 'not', + 'import', + 'export', + 'type', + 'enum', + 'method', + 'var', + 'varip', +]); + +function isKeyword(name: string): boolean { + return KEYWORDS.has(name); +} + +function stripComment(raw: string, tl: TokenizedLine): string { + const c = tl.tokens.find((t) => t.kind === 'comment'); + return c ? raw.slice(0, c.start) : raw; +} + +// Lines belonging to the block that starts at `line`: every following line that is blank or indented deeper than `indent`. +function blockRange(lines: string[], line: number, indent: number): LineRange { + let end = line; + for (let j = line + 1; j < lines.length; j++) { + const l = lines[j]!; + if (!l.trim()) continue; + const ind = l.length - l.trimStart().length; + if (ind <= indent) break; + end = j; + } + return { start: line, end }; +} + +// Joins a function header whose parameter list spans several lines. Returns null when no `)` closes it within 20 lines. +function joinHeader( + lines: string[], + tokenLines: TokenizedLine[], + start: number, +): { text: string; endLine: number } | null { + let text = ''; + for (let j = start; j < Math.min(lines.length, start + 20); j++) { + text += (j === start ? '' : ' ') + stripComment(lines[j]!, tokenLines[j]!).trim(); + const next = tokenLines[j + 1]; + if (!next || next.depthAtStart === 0) return { text, endLine: j }; + } + return null; +} + +export function parseParams(paramText: string): ParamDecl[] { + const parts: string[] = []; + let depth = 0; + let current = ''; + let quote: string | null = null; + for (const ch of paramText) { + if (quote) { + current += ch; + if (ch === quote) quote = null; + continue; + } + if (ch === '"' || ch === "'") quote = ch; + if (ch === '(' || ch === '[' || ch === '<') depth++; + if (ch === ')' || ch === ']' || ch === '>') depth--; + if (ch === ',' && depth === 0) { + parts.push(current); + current = ''; + continue; + } + current += ch; + } + if (current.trim()) parts.push(current); + return parts + .map((p) => p.trim().replace(/^(?:series|simple|const|input)\s+/, '')) + .map((p) => p.match(RE_PARAM)) + .filter((m): m is RegExpMatchArray => !!m) + .map((m) => ({ name: m[2]!, type: m[1] ?? null, default: m[3]?.trim() ?? null })); +} + +export function annotationsAbove(lines: string[], line: number): Annotations { + const docs = emptyAnnotations(); + const block: string[] = []; + for (let j = line - 1; j >= 0; j--) { + const t = lines[j]!.trim(); + if (!t.startsWith('//')) break; + block.unshift(t); + } + let lastKey: + { kind: 'params' | 'fields'; name: string } | 'function' | 'description' | 'returns' | 'type' | 'enum' | null = + null; + for (const text of block) { + const m = text.match(/^\/\/\s*@(\w+)\s*(.*)$/); + if (!m) { + const plain = text.replace(/^\/\/\s?/, ''); + if (lastKey && plain) appendDoc(docs, lastKey, plain); + continue; + } + docs.raw.push(text); + const tag = m[1]!; + const rest = m[2]!.trim(); + if (tag === 'param' || tag === 'field') { + const pm = rest.match(/^(\w+)\s*(.*)$/); + if (!pm) continue; + const bucket = tag === 'param' ? docs.params : docs.fields; + bucket[pm[1]!] = pm[2]!.trim(); + lastKey = { kind: tag === 'param' ? 'params' : 'fields', name: pm[1]! }; + } else if (tag === 'function' || tag === 'description' || tag === 'returns' || tag === 'type' || tag === 'enum') { + docs[tag] = rest; + lastKey = tag; + } else if (tag === 'variable') { + docs.description = rest; + lastKey = 'description'; + } + } + return docs; +} + +function appendDoc(docs: Annotations, key: NonNullable>, text: string): void { + if (typeof key === 'string') docs[key] = docs[key] ? `${docs[key]} ${text}` : text; + else docs[key.kind][key.name] = docs[key.kind][key.name] ? `${docs[key.kind][key.name]} ${text}` : text; +} + +function keyOf(): + { kind: 'params' | 'fields'; name: string } | 'function' | 'description' | 'returns' | 'type' | 'enum' | null { + return null; +} +``` + +Note for the implementer: `keyOf` exists only to name the union type for `appendDoc`; if you prefer, declare `type DocKey = …` and delete `keyOf`. + +- [ ] **Step 5: Run to verify it passes** + +Run: `npx vitest run tests/core/document-model.test.ts` +Expected: PASS (10 tests). If a range assertion is off by one, check the fixture line numbers (0-based) rather than the implementation first. + +- [ ] **Step 6: Commit** + +```bash +npm run format +git add src/extension/core/document-model.ts tests/core/document-model.test.ts tests/core/fixtures +git commit -m "Add document model for functions, types, enums, variables and imports" +``` + +--- + +### Task 6: Call resolver and completion context + +**Files:** + +- Create: `src/extension/core/call-resolver.ts`, `src/extension/core/context.ts`, `tests/core/call-resolver.test.ts`, `tests/core/context.test.ts` + +**Interfaces:** + +- Consumes: `tokenize`, `TokenizedLine`, `Token` (Task 4). +- Produces: + + ```ts + interface CallInfo { + name: string; + argIndex: number; + namedArg: string | null; + usedNamedArgs: string[]; + line: number; + column: number; + } + function enclosingCall(tokenLines: TokenizedLine[], line: number, col: number): CallInfo | null; + type CompletionContext = + | { kind: 'none' } + | { kind: 'annotation'; prefix: string } + | { kind: 'import-path'; prefix: string } + | { kind: 'member'; receiver: string; prefix: string } + | { kind: 'named-arg'; call: CallInfo; prefix: string } + | { kind: 'identifier'; prefix: string }; + function completionContext( + tokenLines: TokenizedLine[], + lineText: string, + line: number, + col: number, + ): CompletionContext; + ``` + +- [ ] **Step 1: Write the failing tests** + +`tests/core/call-resolver.test.ts`: + +```ts +import { describe, expect, it } from 'vitest'; +import { enclosingCall } from '../../src/extension/core/call-resolver'; +import { tokenize } from '../../src/extension/core/tokenizer'; + +const at = (text: string) => { + const line = text.split('\n').length - 1; + const col = text.split('\n').at(-1)!.length; + return enclosingCall(tokenize(text), line, col); +}; + +describe('enclosingCall', () => { + it('finds the callee and argument index', () => { + expect(at('plot(close, ')).toMatchObject({ name: 'plot', argIndex: 1, namedArg: null }); + expect(at('plot(ta.sma(close, ')).toMatchObject({ name: 'ta.sma', argIndex: 1 }); + expect(at('plot(ta.sma(close, 14), ')).toMatchObject({ name: 'plot', argIndex: 1 }); + }); + + it('detects named arguments and remembers used ones', () => { + expect(at('plot(close, title = "x", color = ')).toMatchObject({ + name: 'plot', + argIndex: 2, + namedArg: 'color', + usedNamedArgs: ['title', 'color'], + }); + }); + + it('ignores commas inside strings and brackets', () => { + expect(at('str.format("{0}, {1}", a[1, ')).toMatchObject({ name: 'str.format', argIndex: 1 }); + }); + + it('works across lines', () => { + expect(at('plot(close,\n title = "x",\n ')).toMatchObject({ name: 'plot', argIndex: 2 }); + }); + + it('returns null outside calls or for grouping parens', () => { + expect(at('x = 1 + ')).toBeNull(); + expect(at('x = (a + ')).toBeNull(); + }); + + it('reports the call position', () => { + expect(at(' plot(close, ')).toMatchObject({ line: 0, column: 2 }); + }); +}); +``` + +`tests/core/context.test.ts`: + +```ts +import { describe, expect, it } from 'vitest'; +import { completionContext } from '../../src/extension/core/context'; +import { tokenize } from '../../src/extension/core/tokenizer'; + +const ctx = (text: string) => { + const lines = text.split('\n'); + const line = lines.length - 1; + return completionContext(tokenize(text), lines[line]!, line, lines[line]!.length); +}; + +describe('completionContext', () => { + it('is silent inside strings and plain comments', () => { + expect(ctx('x = "ab')).toEqual({ kind: 'none' }); + expect(ctx('// hello wo')).toEqual({ kind: 'none' }); + }); + + it('detects annotations', () => { + expect(ctx('//@par')).toEqual({ kind: 'annotation', prefix: 'par' }); + expect(ctx('// @')).toEqual({ kind: 'annotation', prefix: '' }); + }); + + it('detects import paths', () => { + expect(ctx('import Trad')).toEqual({ kind: 'import-path', prefix: 'Trad' }); + expect(ctx('import TradingView/ta/')).toEqual({ kind: 'import-path', prefix: 'TradingView/ta/' }); + expect(ctx('import TradingView/ta/14 as ')).toEqual({ kind: 'none' }); + }); + + it('detects member access', () => { + expect(ctx('x = ta.')).toEqual({ kind: 'member', receiver: 'ta', prefix: '' }); + expect(ctx('x = ta.sm')).toEqual({ kind: 'member', receiver: 'ta', prefix: 'sm' }); + expect(ctx('chart.point.')).toEqual({ kind: 'member', receiver: 'chart.point', prefix: '' }); + }); + + it('detects named-argument position inside calls', () => { + expect(ctx('plot(close, ')).toMatchObject({ kind: 'named-arg', prefix: '', call: { name: 'plot', argIndex: 1 } }); + expect(ctx('plot(close, ti')).toMatchObject({ kind: 'named-arg', prefix: 'ti' }); + expect(ctx('plot(close, title = ')).toEqual({ kind: 'identifier', prefix: '' }); + }); + + it('falls back to identifiers', () => { + expect(ctx('x = clo')).toEqual({ kind: 'identifier', prefix: 'clo' }); + expect(ctx('')).toEqual({ kind: 'identifier', prefix: '' }); + }); +}); +``` + +- [ ] **Step 2: Run to verify they fail** + +Run: `npx vitest run tests/core/call-resolver.test.ts tests/core/context.test.ts` +Expected: FAIL, modules not found. + +- [ ] **Step 3: Implement `src/extension/core/call-resolver.ts`** + +```ts +import type { Token, TokenizedLine } from './tokenizer'; + +export interface CallInfo { + name: string; + argIndex: number; + namedArg: string | null; + usedNamedArgs: string[]; + line: number; + column: number; +} + +const MAX_LINES_BACK = 50; + +// Flattens tokens from up to MAX_LINES_BACK lines before the cursor, keeping only what precedes (line, col). +function tokensBefore(tokenLines: TokenizedLine[], line: number, col: number): Token[] { + const out: Token[] = []; + for (let l = Math.max(0, line - MAX_LINES_BACK); l <= line; l++) { + for (const t of tokenLines[l]?.tokens ?? []) { + if (t.kind === 'ws' || t.kind === 'comment') continue; + if (l === line && t.start >= col) break; + if (l === line && t.end > col) { + out.push({ ...t, end: col, text: t.text.slice(0, col - t.start) }); + break; + } + out.push(t); + } + } + return out; +} + +export function enclosingCall(tokenLines: TokenizedLine[], line: number, col: number): CallInfo | null { + const tokens = tokensBefore(tokenLines, line, col); + let depth = 0; + let commas = 0; + let openIndex = -1; + for (let i = tokens.length - 1; i >= 0; i--) { + const t = tokens[i]!; + if (t.kind === 'close') depth++; + else if (t.kind === 'open') { + if (depth > 0) { + depth--; + continue; + } + const callee = tokens[i - 1]; + if (t.text === '(' && callee?.kind === 'ident') { + openIndex = i; + break; + } + // Grouping parens or an index: keep looking outward but reset the comma count for this level. + commas = 0; + continue; + } else if (t.kind === 'comma' && depth === 0) commas++; + } + if (openIndex === -1) return null; + const callee = tokens[openIndex - 1]!; + + // Split the argument region into top-level segments to find named arguments. + const args: Token[][] = [[]]; + let d = 0; + for (const t of tokens.slice(openIndex + 1)) { + if (t.kind === 'open') d++; + if (t.kind === 'close') d--; + if (t.kind === 'comma' && d === 0) { + args.push([]); + continue; + } + args.at(-1)!.push(t); + } + const usedNamedArgs: string[] = []; + for (const seg of args) { + if (seg[0]?.kind === 'ident' && seg[1]?.kind === 'op' && seg[1].text === '=') usedNamedArgs.push(seg[0].text); + } + const current = args.at(-1)!; + const namedArg = + current[0]?.kind === 'ident' && current[1]?.kind === 'op' && current[1].text === '=' ? current[0].text : null; + + return { name: callee.text, argIndex: commas, namedArg, usedNamedArgs, line: callee.line, column: callee.start }; +} +``` + +- [ ] **Step 4: Implement `src/extension/core/context.ts`** + +```ts +import { enclosingCall, type CallInfo } from './call-resolver'; +import { isInStringOrComment, type TokenizedLine } from './tokenizer'; + +export type CompletionContext = + | { kind: 'none' } + | { kind: 'annotation'; prefix: string } + | { kind: 'import-path'; prefix: string } + | { kind: 'member'; receiver: string; prefix: string } + | { kind: 'named-arg'; call: CallInfo; prefix: string } + | { kind: 'identifier'; prefix: string }; + +const RE_ANNOTATION = /\/\/\s*@(\w*)$/; +const RE_IMPORT = /^\s*import\s+([\w\-/.]*)$/; +const RE_MEMBER = /([A-Za-z_][\w.]*)\.(\w*)$/; +const RE_WORD = /(\w*)$/; + +export function completionContext( + tokenLines: TokenizedLine[], + lineText: string, + line: number, + col: number, +): CompletionContext { + const before = lineText.slice(0, col); + const annotation = before.match(RE_ANNOTATION); + if (annotation) return { kind: 'annotation', prefix: annotation[1]! }; + + const tl = tokenLines[line]; + if (tl && isInStringOrComment(tl, col) && !/^\s*\/\/\s*@\w*$/.test(before)) return { kind: 'none' }; + + const imp = before.match(RE_IMPORT); + if (imp) return { kind: 'import-path', prefix: imp[1]! }; + if (/^\s*import\s+\S+\s/.test(before)) return { kind: 'none' }; + + const member = before.match(RE_MEMBER); + if (member) return { kind: 'member', receiver: member[1]!, prefix: member[2]! }; + + const prefix = before.match(RE_WORD)![1]!; + const call = enclosingCall(tokenLines, line, col); + if (call && !call.namedArg) { + const sinceSeparator = before.slice(0, col - prefix.length).trimEnd(); + if (sinceSeparator.endsWith('(') || sinceSeparator.endsWith(',')) return { kind: 'named-arg', call, prefix }; + } + return { kind: 'identifier', prefix }; +} +``` + +- [ ] **Step 5: Run to verify they pass** + +Run: `npx vitest run tests/core/call-resolver.test.ts tests/core/context.test.ts` +Expected: PASS (12 tests). + +- [ ] **Step 6: Commit** + +```bash +npm run format +git add src/extension/core/call-resolver.ts src/extension/core/context.ts tests/core/call-resolver.test.ts tests/core/context.test.ts +git commit -m "Resolve enclosing calls and completion context at a cursor position" +``` + +--- + +### Task 7: Markdown builders + +**Files:** + +- Create: `src/extension/core/markdown.ts`, `tests/core/markdown.test.ts` + +**Interfaces:** + +- Consumes: `RefEntry`, `RefOverload`, `ReferenceIndex` (Task 3); `FunctionSymbol`, `TypeSymbol`, `EnumSymbol`, `ParamDecl` (Task 5). +- Produces: + + ```ts + function entryMarkdown(entry: RefEntry, ref: ReferenceIndex, overloadIndex?: number): string; + function entryDetail(entry: RefEntry): string; // one line: syntax or "kind · type" + function functionSignatureLabel(fn: FunctionSymbol): string; // "weighted(float a, float b, float w = 0.5)" + function functionMarkdown(fn: FunctionSymbol, origin?: string): string; + function typeMarkdown(t: TypeSymbol, origin?: string): string; + function enumMarkdown(e: EnumSymbol, origin?: string): string; + ``` + +- [ ] **Step 1: Write the failing test `tests/core/markdown.test.ts`** + +````ts +import { describe, expect, it } from 'vitest'; +import { buildModel } from '../../src/extension/core/document-model'; +import { + entryDetail, + entryMarkdown, + functionMarkdown, + functionSignatureLabel, +} from '../../src/extension/core/markdown'; +import { loadReference } from '../../src/extension/core/reference'; + +describe('markdown', () => { + const ref = loadReference(); + + it('renders a built-in function', () => { + const md = entryMarkdown(ref.get('ta.sma')!, ref); + expect(md).toContain('```pine\nta.sma(source, length) → series float\n```'); + expect(md).toContain('**Parameters**'); + expect(md).toContain('`source`'); + expect(md).toContain('**Returns**'); + expect(md).toContain('[Reference](https://www.tradingview.com/pine-script-reference/v6/#fun_ta.sma)'); + }); + + it('renders a variable with its type', () => { + const md = entryMarkdown(ref.get('close')!, ref); + expect(md).toContain('```pine\n(variable) close: series float\n```'); + expect(entryDetail(ref.get('close')!)).toBe('series float'); + }); + + it('renders user functions from the document model', () => { + const m = buildModel('//@function Adds.\n//@param a Left.\n//@returns Sum.\nadd(float a, b = 1) =>\n a + b\n'); + const fn = m.functions[0]!; + expect(functionSignatureLabel(fn)).toBe('add(float a, b = 1)'); + const md = functionMarkdown(fn); + expect(md).toContain('```pine\nadd(float a, b = 1)\n```'); + expect(md).toContain('Adds.'); + expect(md).toContain('`a` Left.'); + expect(md).toContain('**Returns** Sum.'); + }); +}); +```` + +- [ ] **Step 2: Run to verify it fails** + +Run: `npx vitest run tests/core/markdown.test.ts` +Expected: FAIL, module not found. + +- [ ] **Step 3: Implement `src/extension/core/markdown.ts`** + +````ts +import type { EnumSymbol, FunctionSymbol, ParamDecl, TypeSymbol } from './document-model'; +import type { RefEntry, ReferenceIndex } from './reference'; + +const fence = (code: string) => '```pine\n' + code + '\n```'; + +export function entryDetail(entry: RefEntry): string { + if (entry.kind === 'function') return entry.overloads[0]?.syntax ?? `${entry.name}()`; + if (entry.type) return entry.type; + return entry.kind; +} + +export function entryMarkdown(entry: RefEntry, ref: ReferenceIndex, overloadIndex = 0): string { + const parts: string[] = []; + if (entry.kind === 'function') { + const overload = entry.overloads[overloadIndex] ?? entry.overloads[0]; + const others = entry.overloads.length > 1 ? `\n\n_${entry.overloads.length} overloads_` : ''; + parts.push(fence(overload?.syntax ?? `${entry.name}()`) + others); + if (entry.description) parts.push(entry.description); + if (overload && overload.params.length) { + parts.push( + '**Parameters**\n\n' + overload.params.map((p) => `- \`${p.name}\` (${p.type}) ${p.description}`).join('\n'), + ); + } + if (overload?.returns?.description || overload?.returns?.type) { + parts.push(`**Returns** ${overload.returns.description || overload.returns.type}`); + } + } else { + const label = + entry.kind === 'type' + ? `(type) ${entry.name}` + : `(${entry.kind}) ${entry.name}${entry.type ? `: ${entry.type}` : ''}`; + parts.push(fence(label)); + if (entry.description) parts.push(entry.description); + if (entry.fields.length) + parts.push('**Fields**\n\n' + entry.fields.map((f) => `- \`${f.name}\` (${f.type}) ${f.description}`).join('\n')); + if (entry.kind === 'keyword' || entry.kind === 'operator') { + const syntax = entry.overloads[0]?.syntax; + if (syntax) parts.push(fence(syntax)); + } + } + if (entry.remarks) parts.push(`**Remarks** ${entry.remarks}`); + parts.push(`[Reference](${ref.url(entry)})`); + return parts.join('\n\n'); +} + +function paramLabel(p: ParamDecl): string { + return `${p.type ? `${p.type} ` : ''}${p.name}${p.default !== null ? ` = ${p.default}` : ''}`; +} + +export function functionSignatureLabel(fn: FunctionSymbol): string { + return `${fn.name}(${fn.params.map(paramLabel).join(', ')})`; +} + +export function functionMarkdown(fn: FunctionSymbol, origin?: string): string { + const parts = [fence(`${fn.isExport ? 'export ' : ''}${fn.isMethod ? 'method ' : ''}${functionSignatureLabel(fn)}`)]; + if (origin) parts.push(`_${origin}_`); + if (fn.docs.function || fn.docs.description) parts.push(fn.docs.function ?? fn.docs.description!); + const documented = fn.params.filter((p) => fn.docs.params[p.name]); + if (documented.length) + parts.push('**Parameters**\n\n' + documented.map((p) => `- \`${p.name}\` ${fn.docs.params[p.name]}`).join('\n')); + if (fn.docs.returns) parts.push(`**Returns** ${fn.docs.returns}`); + return parts.join('\n\n'); +} + +export function typeMarkdown(t: TypeSymbol, origin?: string): string { + const parts = [fence(`${t.isExport ? 'export ' : ''}type ${t.name}`)]; + if (origin) parts.push(`_${origin}_`); + if (t.docs.type || t.docs.description) parts.push(t.docs.type ?? t.docs.description!); + if (t.fields.length) { + parts.push( + '**Fields**\n\n' + + t.fields + .map((f) => `- \`${f.name}\` (${f.type})${t.docs.fields[f.name] ? ` ${t.docs.fields[f.name]}` : ''}`) + .join('\n'), + ); + } + return parts.join('\n\n'); +} + +export function enumMarkdown(e: EnumSymbol, origin?: string): string { + const parts = [fence(`${e.isExport ? 'export ' : ''}enum ${e.name}`)]; + if (origin) parts.push(`_${origin}_`); + if (e.docs.enum || e.docs.description) parts.push(e.docs.enum ?? e.docs.description!); + if (e.members.length) { + parts.push( + '**Members**\n\n' + + e.members + .map( + (m) => + `- \`${m.name}\`${m.title ? ` ${m.title}` : ''}${e.docs.fields[m.name] ? ` ${e.docs.fields[m.name]}` : ''}`, + ) + .join('\n'), + ); + } + return parts.join('\n\n'); +} +```` + +- [ ] **Step 4: Run to verify it passes** + +Run: `npx vitest run tests/core/markdown.test.ts` +Expected: PASS (3 tests). + +- [ ] **Step 5: Commit** + +```bash +npm run format +git add src/extension/core/markdown.ts tests/core/markdown.test.ts +git commit -m "Build hover and completion documentation as markdown" +``` + +--- + +### Task 8: Settings, document cache, and the four read-only providers + +**Files:** + +- Create: `src/extension/vscode/settings.ts`, `src/extension/vscode/document-cache.ts`, `src/extension/providers/completion.ts`, `src/extension/providers/hover.ts`, `src/extension/providers/signature-help.ts`, `src/extension/providers/document-symbol.ts` +- Modify: `src/extension/extension.ts`, `package.json` (configuration contribution) + +**Interfaces:** + +- Consumes: everything from Tasks 3–7. +- Produces: + + ```ts + // settings.ts + interface Settings { + completion: boolean; + hover: boolean; + signatureHelp: boolean; + librariesInclude: string; + librariesRemote: boolean; + diagnosticsRemote: boolean; + } + function getSettings(): Settings; + // document-cache.ts + interface Analysis { + model: DocumentModel; + tokens: TokenizedLine[]; + lines: string[]; + } + function analyze(document: vscode.TextDocument): Analysis; // cached by uri+version, 20 documents LRU + // extension.ts + function registerProviders(context): void; // disposes and re-registers on configuration change + ``` + + Library-aware parts of completion and hover call `LibraryIndex` from Task 12; until then they use an interface stub `libraries: LibraryLookup` with `aliasExports(alias, model)` returning `[]`. Define that interface here: + + ```ts + interface LibraryLookup { + forImport(imp: ImportDecl): Promise; + local(): LibraryInfo[]; + search(prefix: string): Promise; + } + ``` + + and `LibraryInfo` in `core/libraries.ts` (create the type file now; Task 12 fills the logic): + + ```ts + interface LibraryInfo { + id: string; + title: string; + owner: string | null; + version: string | null; + description: string | null; + functions: FunctionSymbol[]; + types: TypeSymbol[]; + enums: EnumSymbol[]; + source: 'local' | 'remote'; + } + ``` + +- [ ] **Step 1: Add the configuration contribution to `package.json`** + +Inside `contributes`: + +```json +"configuration": { + "title": "Pine Script", + "properties": { + "pinescript.completion.enabled": { "type": "boolean", "default": true, "description": "Suggest built-ins, keywords, user symbols and named arguments while typing." }, + "pinescript.hover.enabled": { "type": "boolean", "default": true, "description": "Show documentation when hovering built-ins and user symbols." }, + "pinescript.signatureHelp.enabled": { "type": "boolean", "default": true, "description": "Show parameter hints inside function calls." }, + "pinescript.libraries.local.include": { "type": "string", "default": "**/*.pine", "description": "Glob for workspace files scanned for library() declarations." }, + "pinescript.libraries.remote": { "type": "boolean", "default": true, "description": "Query TradingView for published libraries when completing import statements and hovering imports. Only the typed prefix and library ids are sent." }, + "pinescript.diagnostics.remote": { "type": "boolean", "default": false, "description": "Send the document text to the TradingView compiler to show errors and warnings. Off by default because the full script leaves your machine." } + } +} +``` + +- [ ] **Step 2: Create `src/extension/vscode/settings.ts`** + +```ts +import * as vscode from 'vscode'; + +export interface Settings { + completion: boolean; + hover: boolean; + signatureHelp: boolean; + librariesInclude: string; + librariesRemote: boolean; + diagnosticsRemote: boolean; +} + +export function getSettings(): Settings { + const c = vscode.workspace.getConfiguration('pinescript'); + return { + completion: c.get('completion.enabled', true), + hover: c.get('hover.enabled', true), + signatureHelp: c.get('signatureHelp.enabled', true), + librariesInclude: c.get('libraries.local.include', '**/*.pine'), + librariesRemote: c.get('libraries.remote', true), + diagnosticsRemote: c.get('diagnostics.remote', false), + }; +} +``` + +- [ ] **Step 3: Create `src/extension/vscode/document-cache.ts`** + +```ts +import * as vscode from 'vscode'; +import { buildModel, type DocumentModel } from '../core/document-model'; +import { tokenize, type TokenizedLine } from '../core/tokenizer'; + +export interface Analysis { + model: DocumentModel; + tokens: TokenizedLine[]; + lines: string[]; +} + +const MAX = 20; +const cache = new Map(); + +export function analyze(document: vscode.TextDocument): Analysis { + const key = document.uri.toString(); + const hit = cache.get(key); + if (hit && hit.version === document.version) return hit.analysis; + const text = document.getText(); + const analysis: Analysis = { model: buildModel(text), tokens: tokenize(text), lines: text.split(/\r?\n/) }; + cache.delete(key); + cache.set(key, { version: document.version, analysis }); + if (cache.size > MAX) cache.delete(cache.keys().next().value!); + return analysis; +} + +export function forget(uri: vscode.Uri): void { + cache.delete(uri.toString()); +} +``` + +- [ ] **Step 4: Create the `LibraryInfo` type and `LibraryLookup` stub** + +`src/extension/core/libraries.ts` (types only for now): + +```ts +import type { EnumSymbol, FunctionSymbol, ImportDecl, TypeSymbol } from './document-model'; + +export interface LibraryInfo { + id: string; + title: string; + owner: string | null; + version: string | null; + description: string | null; + functions: FunctionSymbol[]; + types: TypeSymbol[]; + enums: EnumSymbol[]; + source: 'local' | 'remote'; +} + +export interface LibraryLookup { + forImport(imp: ImportDecl): Promise; + local(): LibraryInfo[]; + search(prefix: string): Promise; +} + +export const noLibraries: LibraryLookup = { + forImport: async () => null, + local: () => [], + search: async () => [], +}; +``` + +- [ ] **Step 5: Create `src/extension/providers/completion.ts`** + +```ts +import * as vscode from 'vscode'; +import { completionContext } from '../core/context'; +import type { DocumentModel, EnumSymbol, FunctionSymbol, TypeSymbol } from '../core/document-model'; +import { visibleVariables } from '../core/document-model'; +import type { LibraryInfo, LibraryLookup } from '../core/libraries'; +import { + entryDetail, + entryMarkdown, + enumMarkdown, + functionMarkdown, + functionSignatureLabel, + typeMarkdown, +} from '../core/markdown'; +import type { RefEntry, ReferenceIndex } from '../core/reference'; +import { analyze } from '../vscode/document-cache'; + +const KEYWORDS = [ + 'and', + 'or', + 'not', + 'if', + 'else', + 'for', + 'to', + 'by', + 'in', + 'while', + 'switch', + 'once', + 'var', + 'varip', + 'import', + 'export', + 'method', + 'type', + 'enum', + 'true', + 'false', + 'na', + 'series', + 'simple', + 'const', + 'input', +]; +const TYPES = [ + 'int', + 'float', + 'bool', + 'string', + 'color', + 'line', + 'label', + 'box', + 'table', + 'linefill', + 'polyline', + 'array', + 'matrix', + 'map', + 'chart.point', + 'footprint', + 'volume_row', +]; + +const KIND: Record = { + function: vscode.CompletionItemKind.Function, + variable: vscode.CompletionItemKind.Variable, + constant: vscode.CompletionItemKind.Constant, + keyword: vscode.CompletionItemKind.Keyword, + type: vscode.CompletionItemKind.Class, + annotation: vscode.CompletionItemKind.Keyword, + operator: vscode.CompletionItemKind.Operator, +}; + +export class PineCompletionProvider implements vscode.CompletionItemProvider { + constructor( + private readonly ref: ReferenceIndex, + private readonly libraries: LibraryLookup, + ) {} + + async provideCompletionItems( + document: vscode.TextDocument, + position: vscode.Position, + ): Promise { + const { model, tokens, lines } = analyze(document); + const ctx = completionContext(tokens, lines[position.line] ?? '', position.line, position.character); + switch (ctx.kind) { + case 'none': + return []; + case 'annotation': + return this.ref.byKind('annotation').map((e) => this.entryItem(e, e.name.replace(/^@/, ''))); + case 'import-path': + return this.importItems(ctx.prefix); + case 'member': + return this.memberItems(ctx.receiver, model); + case 'named-arg': + return [ + ...this.namedArgItems(ctx.call.name, ctx.call.usedNamedArgs, model), + ...this.identifierItems(model, position.line), + ]; + case 'identifier': + return this.identifierItems(model, position.line); + } + } + + private entryItem( + entry: RefEntry, + label = entry.name.includes('.') ? entry.name.slice(entry.name.lastIndexOf('.') + 1) : entry.name, + ): vscode.CompletionItem { + const item = new vscode.CompletionItem(label, KIND[entry.kind]); + item.detail = entryDetail(entry); + item.documentation = new vscode.MarkdownString(entryMarkdown(entry, this.ref)); + if (entry.kind === 'function') { + item.insertText = new vscode.SnippetString(`${label}($1)`); + item.command = { command: 'editor.action.triggerParameterHints', title: 'Trigger parameter hints' }; + } + return item; + } + + private namespaceItem(name: string): vscode.CompletionItem { + const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Module); + item.detail = 'namespace'; + return item; + } + + private identifierItems(model: DocumentModel, line: number): vscode.CompletionItem[] { + const items: vscode.CompletionItem[] = []; + for (const ns of this.ref.childNamespaces('')) items.push(this.namespaceItem(ns)); + for (const e of this.ref.bare()) items.push(this.entryItem(e)); + for (const k of KEYWORDS) items.push(new vscode.CompletionItem(k, vscode.CompletionItemKind.Keyword)); + for (const t of TYPES) items.push(new vscode.CompletionItem(t, vscode.CompletionItemKind.Class)); + for (const f of model.functions) items.push(userFunctionItem(f)); + for (const t of model.types) items.push(userTypeItem(t)); + for (const e of model.enums) items.push(userEnumItem(e)); + for (const v of visibleVariables(model, line)) { + const item = new vscode.CompletionItem(v.name, vscode.CompletionItemKind.Variable); + item.detail = v.declaredType ?? undefined; + items.push(item); + } + for (const imp of model.imports) { + if (imp.alias) { + const item = new vscode.CompletionItem(imp.alias, vscode.CompletionItemKind.Module); + item.detail = `${imp.owner}/${imp.name}/${imp.version}`; + items.push(item); + } + } + return items; + } + + private async memberItems(receiver: string, model: DocumentModel): Promise { + const items: vscode.CompletionItem[] = []; + for (const ns of this.ref.childNamespaces(receiver)) items.push(this.namespaceItem(ns)); + for (const e of this.ref.members(receiver)) items.push(this.entryItem(e)); + + const type = model.types.find((t) => t.name === receiver); + if (type) { + const ctor = new vscode.CompletionItem('new', vscode.CompletionItemKind.Constructor); + ctor.insertText = new vscode.SnippetString('new($1)'); + ctor.detail = `${type.name}.new(${type.fields.map((f) => f.name).join(', ')})`; + items.push(ctor); + for (const f of type.fields) + items.push( + Object.assign(new vscode.CompletionItem(f.name, vscode.CompletionItemKind.Field), { detail: f.type }), + ); + } + const en = model.enums.find((e) => e.name === receiver); + if (en) + for (const m of en.members) items.push(new vscode.CompletionItem(m.name, vscode.CompletionItemKind.EnumMember)); + + const imp = model.imports.find((i) => i.alias === receiver); + if (imp) { + const lib = await this.libraries.forImport(imp); + if (lib) items.push(...libraryExportItems(lib)); + } + // Methods declared on user types: `x.` where x is a variable of that type is out of scope; methods appear as functions. + return items; + } + + private namedArgItems(callee: string, used: string[], model: DocumentModel): vscode.CompletionItem[] { + const names = new Set(); + const entry = this.ref.get(callee, 'function'); + if (entry) for (const o of entry.overloads) for (const p of o.params) names.add(p.name); + const fn = model.functions.find((f) => f.name === callee); + if (fn) for (const p of fn.params) names.add(p.name); + return [...names] + .filter((n) => !used.includes(n)) + .map((n) => { + const item = new vscode.CompletionItem(`${n}=`, vscode.CompletionItemKind.Property); + item.insertText = `${n} = `; + item.sortText = `0${n}`; + item.detail = 'named argument'; + return item; + }); + } + + private async importItems(prefix: string): Promise { + const local = this.libraries.local(); + const remote = await this.libraries.search(prefix); + return [...local, ...remote].map((lib) => { + const item = new vscode.CompletionItem(lib.id, vscode.CompletionItemKind.Module); + item.detail = lib.source === 'local' ? 'workspace library' : 'TradingView library'; + if (lib.description) item.documentation = new vscode.MarkdownString(lib.description); + item.range = undefined; + item.filterText = lib.id; + return item; + }); + } +} + +export function userFunctionItem(f: FunctionSymbol, origin?: string): vscode.CompletionItem { + const item = new vscode.CompletionItem( + f.name, + f.isMethod ? vscode.CompletionItemKind.Method : vscode.CompletionItemKind.Function, + ); + item.detail = functionSignatureLabel(f); + item.documentation = new vscode.MarkdownString(functionMarkdown(f, origin)); + item.insertText = new vscode.SnippetString(`${f.name}($1)`); + item.command = { command: 'editor.action.triggerParameterHints', title: 'Trigger parameter hints' }; + return item; +} + +export function userTypeItem(t: TypeSymbol, origin?: string): vscode.CompletionItem { + const item = new vscode.CompletionItem(t.name, vscode.CompletionItemKind.Class); + item.documentation = new vscode.MarkdownString(typeMarkdown(t, origin)); + return item; +} + +export function userEnumItem(e: EnumSymbol, origin?: string): vscode.CompletionItem { + const item = new vscode.CompletionItem(e.name, vscode.CompletionItemKind.Enum); + item.documentation = new vscode.MarkdownString(enumMarkdown(e, origin)); + return item; +} + +export function libraryExportItems(lib: LibraryInfo): vscode.CompletionItem[] { + const origin = `from ${lib.id}`; + return [ + ...lib.functions.map((f) => userFunctionItem(f, origin)), + ...lib.types.map((t) => userTypeItem(t, origin)), + ...lib.enums.map((e) => userEnumItem(e, origin)), + ]; +} +``` + +- [ ] **Step 6: Create `src/extension/providers/hover.ts`** + +````ts +import * as vscode from 'vscode'; +import { visibleVariables } from '../core/document-model'; +import type { LibraryLookup } from '../core/libraries'; +import { entryMarkdown, enumMarkdown, functionMarkdown, typeMarkdown } from '../core/markdown'; +import type { ReferenceIndex } from '../core/reference'; +import { isInStringOrComment, wordAt } from '../core/tokenizer'; +import { analyze } from '../vscode/document-cache'; + +export class PineHoverProvider implements vscode.HoverProvider { + constructor( + private readonly ref: ReferenceIndex, + private readonly libraries: LibraryLookup, + ) {} + + async provideHover(document: vscode.TextDocument, position: vscode.Position): Promise { + const { model, tokens, lines } = analyze(document); + const lineText = lines[position.line] ?? ''; + const tl = tokens[position.line]; + + const imp = model.imports.find((i) => i.line === position.line); + if (imp) { + const lib = await this.libraries.forImport(imp); + if (!lib) return null; + const exports = [ + ...lib.functions.map((f) => `\`${f.name}()\``), + ...lib.types.map((t) => `\`${t.name}\``), + ...lib.enums.map((e) => `\`${e.name}\``), + ]; + const md = [ + `**${lib.title}** _(${lib.source === 'local' ? 'workspace' : 'TradingView'})_`, + lib.description ?? '', + exports.length ? `Exports: ${exports.join(', ')}` : '', + ] + .filter(Boolean) + .join('\n\n'); + return new vscode.Hover(new vscode.MarkdownString(md)); + } + + if (tl && isInStringOrComment(tl, position.character)) { + const ann = lineText.slice(0, position.character + 1).match(/\/\/\s*(@\w+)$/) ?? lineText.match(/\/\/\s*(@\w+)/); + const entry = ann ? this.ref.get(ann[1]!, 'annotation') : undefined; + return entry ? new vscode.Hover(new vscode.MarkdownString(entryMarkdown(entry, this.ref))) : null; + } + + const word = wordAt(lineText, position.character); + if (!word) return null; + const range = new vscode.Range(position.line, word.start, position.line, word.end); + const md = new vscode.MarkdownString(); + md.isTrusted = true; + + // Alias member: ma.weighted + const dot = word.text.indexOf('.'); + if (dot > 0) { + const alias = word.text.slice(0, dot); + const member = word.text.slice(dot + 1); + const imp = model.imports.find((i) => i.alias === alias); + if (imp) { + const lib = await this.libraries.forImport(imp); + const f = lib?.functions.find((f) => f.name === member); + if (f) return new vscode.Hover(new vscode.MarkdownString(functionMarkdown(f, `from ${lib!.id}`)), range); + const t = lib?.types.find((t) => t.name === member); + if (t) return new vscode.Hover(new vscode.MarkdownString(typeMarkdown(t, `from ${lib!.id}`)), range); + const e = lib?.enums.find((e) => e.name === member); + if (e) return new vscode.Hover(new vscode.MarkdownString(enumMarkdown(e, `from ${lib!.id}`)), range); + } + } + + // User symbols. + const fn = model.functions.find( + (f) => f.name === word.text || (word.text.includes('.') && f.isMethod && word.text.endsWith(`.${f.name}`)), + ); + if (fn) return new vscode.Hover(new vscode.MarkdownString(functionMarkdown(fn)), range); + const type = model.types.find((t) => t.name === word.text); + if (type) return new vscode.Hover(new vscode.MarkdownString(typeMarkdown(type)), range); + const en = model.enums.find((e) => e.name === word.text || word.text.startsWith(`${e.name}.`)); + if (en) return new vscode.Hover(new vscode.MarkdownString(enumMarkdown(en)), range); + const variable = visibleVariables(model, position.line).find((v) => v.name === word.text); + if (variable) { + const decl = `${variable.qualifier ? `${variable.qualifier} ` : ''}${variable.declaredType ? `${variable.declaredType} ` : ''}${variable.name}${variable.initializer ? ` = ${variable.initializer}` : ''}`; + return new vscode.Hover(new vscode.MarkdownString('```pine\n' + decl + '\n```'), range); + } + + // Built-ins: longest dotted match first, then shorter prefixes ending at the cursor. + const candidates = [word.text]; + const parts = word.text.split('.'); + for (let i = parts.length - 1; i > 0; i--) candidates.push(parts.slice(0, i).join('.')); + for (const name of candidates) { + const entries = this.ref.getAll(name); + if (entries.length) { + const preferred = this.ref.get(name)!; + const others = entries.filter((e) => e !== preferred); + const text = [entryMarkdown(preferred, this.ref), ...others.map((e) => entryMarkdown(e, this.ref))].join( + '\n\n---\n\n', + ); + return new vscode.Hover(new vscode.MarkdownString(text), range); + } + } + return null; + } +} +```` + +- [ ] **Step 7: Create `src/extension/providers/signature-help.ts`** + +```ts +import * as vscode from 'vscode'; +import { enclosingCall } from '../core/call-resolver'; +import type { LibraryLookup } from '../core/libraries'; +import { functionSignatureLabel } from '../core/markdown'; +import type { ReferenceIndex } from '../core/reference'; +import { analyze } from '../vscode/document-cache'; +import type { FunctionSymbol } from '../core/document-model'; + +export class PineSignatureHelpProvider implements vscode.SignatureHelpProvider { + constructor( + private readonly ref: ReferenceIndex, + private readonly libraries: LibraryLookup, + ) {} + + async provideSignatureHelp( + document: vscode.TextDocument, + position: vscode.Position, + ): Promise { + const { model, tokens } = analyze(document); + const call = enclosingCall(tokens, position.line, position.character); + if (!call) return null; + + const help = new vscode.SignatureHelp(); + const entry = this.ref.get(call.name, 'function'); + if (entry) { + for (const o of entry.overloads) { + const sig = new vscode.SignatureInformation(o.syntax, new vscode.MarkdownString(entry.description)); + sig.parameters = o.params.map( + (p) => new vscode.ParameterInformation(p.name, new vscode.MarkdownString(`(${p.type}) ${p.description}`)), + ); + help.signatures.push(sig); + } + } else { + let fn: FunctionSymbol | undefined = model.functions.find((f) => f.name === call.name); + let origin: string | undefined; + const dot = call.name.indexOf('.'); + if (!fn && dot > 0) { + const imp = model.imports.find((i) => i.alias === call.name.slice(0, dot)); + const lib = imp ? await this.libraries.forImport(imp) : null; + fn = lib?.functions.find((f) => f.name === call.name.slice(dot + 1)); + origin = lib?.id; + if (!fn) { + const type = model.types.find((t) => t.name === call.name.slice(0, dot)); + if (type && call.name.endsWith('.new')) { + const sig = new vscode.SignatureInformation( + `${type.name}.new(${type.fields.map((f) => `${f.type} ${f.name}${f.default ? ` = ${f.default}` : ''}`).join(', ')})`, + ); + sig.parameters = type.fields.map((f) => new vscode.ParameterInformation(f.name, type.docs.fields[f.name])); + help.signatures.push(sig); + } + } + } + if (fn) { + const sig = new vscode.SignatureInformation( + functionSignatureLabel(fn), + new vscode.MarkdownString( + origin ? `_from ${origin}_\n\n${fn.docs.function ?? ''}` : (fn.docs.function ?? ''), + ), + ); + sig.parameters = fn.params.map((p) => new vscode.ParameterInformation(p.name, fn!.docs.params[p.name])); + help.signatures.push(sig); + } + } + if (!help.signatures.length) return null; + + // Active signature: first whose parameter count covers the current argument; active parameter: named or positional. + help.activeSignature = Math.max( + 0, + help.signatures.findIndex((s) => s.parameters.length > call.argIndex), + ); + const active = help.signatures[help.activeSignature]!; + const named = call.namedArg ? active.parameters.findIndex((p) => p.label === call.namedArg) : -1; + help.activeParameter = named >= 0 ? named : Math.min(call.argIndex, Math.max(0, active.parameters.length - 1)); + return help; + } +} +``` + +- [ ] **Step 8: Create `src/extension/providers/document-symbol.ts`** + +```ts +import * as vscode from 'vscode'; +import { analyze } from '../vscode/document-cache'; + +export class PineDocumentSymbolProvider implements vscode.DocumentSymbolProvider { + provideDocumentSymbols(document: vscode.TextDocument): vscode.DocumentSymbol[] { + const { model, lines } = analyze(document); + const symbols: vscode.DocumentSymbol[] = []; + const lineRange = (start: number, end: number) => new vscode.Range(start, 0, end, lines[end]?.length ?? 0); + + for (const f of model.functions) { + symbols.push( + new vscode.DocumentSymbol( + f.name, + f.isMethod ? 'method' : 'function', + f.isMethod ? vscode.SymbolKind.Method : vscode.SymbolKind.Function, + lineRange(f.range.start, f.range.end), + lineRange(f.line, f.line), + ), + ); + } + for (const t of model.types) { + const s = new vscode.DocumentSymbol( + t.name, + 'type', + vscode.SymbolKind.Struct, + lineRange(t.range.start, t.range.end), + lineRange(t.line, t.line), + ); + s.children = t.fields.map( + (f, i) => + new vscode.DocumentSymbol( + f.name, + f.type, + vscode.SymbolKind.Field, + lineRange(t.line + 1 + i, t.line + 1 + i), + lineRange(t.line + 1 + i, t.line + 1 + i), + ), + ); + symbols.push(s); + } + for (const e of model.enums) { + const s = new vscode.DocumentSymbol( + e.name, + 'enum', + vscode.SymbolKind.Enum, + lineRange(e.range.start, e.range.end), + lineRange(e.line, e.line), + ); + s.children = e.members.map( + (m, i) => + new vscode.DocumentSymbol( + m.name, + m.title ?? '', + vscode.SymbolKind.EnumMember, + lineRange(e.line + 1 + i, e.line + 1 + i), + lineRange(e.line + 1 + i, e.line + 1 + i), + ), + ); + symbols.push(s); + } + for (const v of model.variables) { + if (model.functions.some((f) => v.line > f.line && v.line <= f.range.end)) continue; // locals stay out of the outline + symbols.push( + new vscode.DocumentSymbol( + v.name, + v.declaredType ?? '', + vscode.SymbolKind.Variable, + lineRange(v.line, v.line), + new vscode.Range(v.line, v.column, v.line, v.column + v.name.length), + ), + ); + } + return symbols.sort((a, b) => a.range.start.line - b.range.start.line); + } +} +``` + +- [ ] **Step 9: Wire everything in `src/extension/extension.ts`** + +```ts +import * as vscode from 'vscode'; +import { noLibraries, type LibraryLookup } from './core/libraries'; +import { loadReference } from './core/reference'; +import { PineCompletionProvider } from './providers/completion'; +import { PineDocumentSymbolProvider } from './providers/document-symbol'; +import { PineHoverProvider } from './providers/hover'; +import { PineSignatureHelpProvider } from './providers/signature-help'; +import { forget } from './vscode/document-cache'; +import { log, output } from './vscode/output'; +import { getSettings } from './vscode/settings'; + +const SELECTOR: vscode.DocumentSelector = { language: 'pinescript' }; +let providerDisposables: vscode.Disposable[] = []; + +export function activate(context: vscode.ExtensionContext): void { + context.subscriptions.push(output()); + const libraries: LibraryLookup = noLibraries; // replaced in Task 12 + + registerProviders(context, libraries); + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('pinescript')) registerProviders(context, libraries); + }), + vscode.workspace.onDidCloseTextDocument((d) => forget(d.uri)), + vscode.languages.registerDocumentSymbolProvider(SELECTOR, new PineDocumentSymbolProvider()), + ); + log('Pine Script extension activated'); +} + +export function registerProviders(context: vscode.ExtensionContext, libraries: LibraryLookup): void { + for (const d of providerDisposables) d.dispose(); + providerDisposables = []; + const settings = getSettings(); + const ref = loadReference(); + if (settings.completion) { + providerDisposables.push( + vscode.languages.registerCompletionItemProvider( + SELECTOR, + new PineCompletionProvider(ref, libraries), + '.', + '/', + '@', + '(', + ',', + ), + ); + } + if (settings.hover) + providerDisposables.push(vscode.languages.registerHoverProvider(SELECTOR, new PineHoverProvider(ref, libraries))); + if (settings.signatureHelp) + providerDisposables.push( + vscode.languages.registerSignatureHelpProvider(SELECTOR, new PineSignatureHelpProvider(ref, libraries), '(', ','), + ); + context.subscriptions.push(...providerDisposables); +} + +export function deactivate(): void {} +``` + +- [ ] **Step 10: Type-check, build, and try it** + +Run: `npm run typecheck && npm run build:extension && npm run test:core` +Expected: clean. Then press F5 in VS Code, open `tests/core/fixtures/consumer.pine`, and check: typing `ta.` lists members with documentation; hovering `close` shows the type; typing `plot(` shows signature help; the Outline view lists `length`, `acc`, `src`, `fast`. Fix anything that misbehaves before committing. + +- [ ] **Step 11: Commit** + +```bash +npm run format +git add -A +git commit -m "Add completion, hover, signature help and outline providers" +``` + +--- + +### Task 9: Type inference and the "Add Type Annotations" command + +**Files:** + +- Create: `src/extension/core/type-inference.ts`, `src/extension/commands/add-type-annotations.ts`, `tests/core/type-inference.test.ts` +- Modify: `src/extension/extension.ts`, `package.json` (commands, menus) + +**Interfaces:** + +- Consumes: `ReferenceIndex` (Task 3), `DocumentModel`, `VariableSymbol`, `visibleVariables` (Task 5), `tokenize` (Task 4). +- Produces: + + ```ts + interface InferenceScope { + ref: ReferenceIndex; + model: DocumentModel; + line: number; + compilerTypes?: ReadonlyMap; + } + function inferType(expr: string, scope: InferenceScope): string | null; // base type or null + interface AnnotationEdit { + line: number; + column: number; + insert: string; + } // insert "float " before the name + function planTypeAnnotations( + model: DocumentModel, + ref: ReferenceIndex, + lines: [number, number] | null, + compilerTypes?: ReadonlyMap, + ): { edits: AnnotationEdit[]; skipped: string[] }; + ``` + +- [ ] **Step 1: Write the failing test `tests/core/type-inference.test.ts`** + +```ts +import { describe, expect, it } from 'vitest'; +import { buildModel } from '../../src/extension/core/document-model'; +import { loadReference } from '../../src/extension/core/reference'; +import { inferType, planTypeAnnotations } from '../../src/extension/core/type-inference'; + +const ref = loadReference(); +const scopeFor = (text: string, line = 99) => ({ ref, model: buildModel(text), line }); + +describe('inferType', () => { + const s = scopeFor(''); + it.each([ + ['1', 'int'], + ['1.5', 'float'], + ['"x"', 'string'], + ['true', 'bool'], + ['#ff0000', 'color'], + ['na', null], + ['close', 'float'], + ['bar_index', 'int'], + ['color.red', 'color'], + ['ta.sma(close, 14)', 'float'], + ['input.int(14)', 'int'], + ['input.string("a")', 'string'], + ['input.source(close)', 'float'], + ['label.new(bar_index, high)', 'label'], + ['array.new()', 'array'], + ['close > open', 'bool'], + ['a and b', 'bool'], + ['not x', 'bool'], + ['1 + 2', 'int'], + ['1 + 2.0', 'float'], + ['"a" + str.tostring(1)', 'string'], + ['close > open ? 1 : 0', 'int'], + ['close > open ? 1 : 0.5', 'float'], + ['close > open ? 1 : "x"', null], + ['unknown_fn(1)', null], + ])('%s → %s', (expr, expected) => { + expect(inferType(expr, s)).toBe(expected); + }); + + it('uses declared types of earlier variables and user types', () => { + const text = 'type Point\n float x\nfloat a = 1\np = Point.new(1)\nb = a * 2\n'; + const s = scopeFor(text, 4); + expect(inferType('a', s)).toBe('float'); + expect(inferType('Point.new(1)', s)).toBe('Point'); + expect(inferType('a + p.x', s)).toBe(null); // field access is not inferred + }); + + it('prefers compiler-provided types', () => { + const s = { ...scopeFor('x = mystery()'), compilerTypes: new Map([['x', 'series string']]) }; + expect(inferType('mystery()', { ...s, line: 0 })).toBe(null); + const plan = planTypeAnnotations(s.model, ref, null, s.compilerTypes); + expect(plan.edits).toEqual([{ line: 0, column: 0, insert: 'string ' }]); + }); +}); + +describe('planTypeAnnotations', () => { + it('annotates inferable declarations and reports the rest', () => { + const text = 'length = input.int(14)\nvar acc = 0.0\nfloat done = 1\nsrc = close\nweird = foo(1)\n'; + const plan = planTypeAnnotations(buildModel(text), ref, null); + expect(plan.edits).toEqual([ + { line: 0, column: 0, insert: 'int ' }, + { line: 1, column: 4, insert: 'float ' }, + { line: 3, column: 0, insert: 'float ' }, + ]); + expect(plan.skipped).toEqual(['weird']); + }); + + it('respects a line range', () => { + const text = 'a = 1\nb = 2\nc = 3\n'; + const plan = planTypeAnnotations(buildModel(text), ref, [1, 1]); + expect(plan.edits.map((e) => e.line)).toEqual([1]); + }); +}); +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `npx vitest run tests/core/type-inference.test.ts` +Expected: FAIL, module not found. + +- [ ] **Step 3: Implement `src/extension/core/type-inference.ts`** + +```ts +import { visibleVariables, type DocumentModel } from './document-model'; +import { ReferenceIndex } from './reference'; +import { tokenize, type Token } from './tokenizer'; + +export interface InferenceScope { + ref: ReferenceIndex; + model: DocumentModel; + line: number; + compilerTypes?: ReadonlyMap; +} + +export interface AnnotationEdit { + line: number; + column: number; + insert: string; +} + +const INPUT_TYPES: Record = { + 'input.int': 'int', + 'input.float': 'float', + 'input.bool': 'bool', + 'input.string': 'string', + 'input.color': 'color', + 'input.source': 'float', + 'input.timeframe': 'string', + 'input.symbol': 'string', + 'input.session': 'string', + 'input.price': 'float', + 'input.time': 'int', + 'input.text_area': 'string', + 'input.enum': 'enum', + input: 'float', +}; +const COMPARISON = new Set(['==', '!=', '<', '<=', '>', '>=']); +const NUMERIC = new Set(['+', '-', '*', '/', '%']); + +export function inferType(expr: string, scope: InferenceScope): string | null { + const tokens = tokenize(expr)[0]?.tokens.filter((t) => t.kind !== 'ws' && t.kind !== 'comment') ?? []; + if (!tokens.length) return null; + const type = inferTokens(tokens, scope); + return type === 'na' ? null : type; +} + +function inferTokens(tokens: Token[], scope: InferenceScope): string | null { + // Unwrap a single outer group: (expr) + if (tokens[0]?.text === '(' && matchingClose(tokens, 0) === tokens.length - 1) + return inferTokens(tokens.slice(1, -1), scope); + + // Ternary at depth 0: cond ? a : b + const q = indexAtDepth(tokens, (t) => t.text === '?'); + if (q > 0) { + const c = indexAtDepth(tokens.slice(q + 1), (t) => t.text === ':'); + if (c > 0) { + const a = inferTokens(tokens.slice(q + 1, q + 1 + c), scope); + const b = inferTokens(tokens.slice(q + 2 + c), scope); + return unify(a, b); + } + } + // Logical operators and `not` yield bool. + if (indexAtDepth(tokens, (t) => t.kind === 'ident' && (t.text === 'and' || t.text === 'or')) >= 0) return 'bool'; + if (tokens[0]?.kind === 'ident' && tokens[0].text === 'not') return 'bool'; + if (indexAtDepth(tokens, (t) => t.kind === 'op' && COMPARISON.has(t.text)) > 0) return 'bool'; + + // Binary arithmetic at depth 0, lowest precedence first (+,- before *,/,%). + for (const ops of [ + ['+', '-'], + ['*', '/', '%'], + ]) { + const i = lastIndexAtDepth(tokens, (t) => t.kind === 'op' && ops.includes(t.text)); + if (i > 0) { + const left = inferTokens(tokens.slice(0, i), scope); + const right = inferTokens(tokens.slice(i + 1), scope); + if (tokens[i]!.text === '+' && (left === 'string' || right === 'string')) return 'string'; + return numeric(left, right); + } + } + // Unary minus. + if (tokens[0]?.kind === 'op' && tokens[0].text === '-' && tokens.length > 1) + return inferTokens(tokens.slice(1), scope); + + const first = tokens[0]!; + if (first.kind === 'number') return first.text.startsWith('#') ? 'color' : /[.eE]/.test(first.text) ? 'float' : 'int'; + if (first.kind === 'string') return 'string'; + if (first.kind !== 'ident') return null; + + // History reference x[1] keeps the type of x. + const indexed = tokens.length > 1 && tokens[1]!.text === '[' && matchingClose(tokens, 1) === tokens.length - 1; + if (indexed) return inferTokens(tokens.slice(0, 1), scope); + + const name = first.text; + const isCall = tokens[1]?.text === '(' || tokens[1]?.text === '<'; + if (tokens.length === 1 || !isCall) { + if (tokens.length !== 1) return null; // something after an identifier we do not model + if (name === 'true' || name === 'false') return 'bool'; + if (name === 'na') return 'na'; + const variable = visibleVariables(scope.model, scope.line) + .filter((v) => v.line < scope.line || scope.line === 99) + .find((v) => v.name === name); + if (variable?.declaredType) return variable.declaredType; + const entry = scope.ref.get(name, 'variable') ?? scope.ref.get(name, 'constant'); + if (entry?.type) return ReferenceIndex.baseType(entry.type); + const en = scope.model.enums.find((e) => name.startsWith(`${e.name}.`)); + if (en) return en.name; + return null; + } + + // Calls. + if (name in INPUT_TYPES) return INPUT_TYPES[name]!; + if (name.endsWith('.new')) { + const owner = name.slice(0, -4); + if (scope.model.types.some((t) => t.name === owner)) return owner; + if (tokens[1]?.text === '<') { + const close = tokens.findIndex((t) => t.text === '>'); + if (close > 1) + return `${owner}<${tokens + .slice(2, close) + .map((t) => t.text) + .join('')}>`; + } + } + const userFn = scope.model.functions.find((f) => f.name === name); + if (userFn) return null; // user function return types need the compiler + const entry = scope.ref.get(name, 'function'); + if (entry) { + const argCount = countArgs(tokens); + const overload = entry.overloads.find((o) => o.params.length >= argCount) ?? entry.overloads[0]; + const ret = overload?.returns?.type; + if (!ret || ret === 'void') return null; + const base = ReferenceIndex.baseType(ret); + return base.includes(' ') || base === 'plot' || base === 'hline' ? null : base; + } + return null; +} + +function countArgs(tokens: Token[]): number { + const open = tokens.findIndex((t) => t.text === '('); + if (open < 0) return 0; + const close = matchingClose(tokens, open); + const inner = tokens.slice(open + 1, close); + if (!inner.length) return 0; + let depth = 0; + let n = 1; + for (const t of inner) { + if (t.kind === 'open') depth++; + else if (t.kind === 'close') depth--; + else if (t.kind === 'comma' && depth === 0) n++; + } + return n; +} + +function matchingClose(tokens: Token[], openIndex: number): number { + let depth = 0; + for (let i = openIndex; i < tokens.length; i++) { + if (tokens[i]!.kind === 'open') depth++; + if (tokens[i]!.kind === 'close') depth--; + if (depth === 0) return i; + } + return -1; +} + +function indexAtDepth(tokens: Token[], pred: (t: Token) => boolean): number { + let depth = 0; + for (let i = 0; i < tokens.length; i++) { + const t = tokens[i]!; + if (t.kind === 'open') depth++; + else if (t.kind === 'close') depth--; + else if (depth === 0 && pred(t)) return i; + } + return -1; +} + +function lastIndexAtDepth(tokens: Token[], pred: (t: Token) => boolean): number { + let depth = 0; + let found = -1; + for (let i = 0; i < tokens.length; i++) { + const t = tokens[i]!; + if (t.kind === 'open') depth++; + else if (t.kind === 'close') depth--; + else if (depth === 0 && pred(t) && i > 0 && tokens[i - 1]!.kind !== 'op') found = i; + } + return found; +} + +function unify(a: string | null, b: string | null): string | null { + if (a === 'na') return b === 'na' ? null : b; + if (b === 'na') return a; + if (a === b) return a; + if ((a === 'int' && b === 'float') || (a === 'float' && b === 'int')) return 'float'; + return null; +} + +function numeric(a: string | null, b: string | null): string | null { + if (a === 'na') a = null; + if (b === 'na') b = null; + if (a === 'float' || b === 'float') return a && b ? 'float' : null; + if (a === 'int' && b === 'int') return 'int'; + return null; +} + +export function planTypeAnnotations( + model: DocumentModel, + ref: ReferenceIndex, + lines: [number, number] | null, + compilerTypes?: ReadonlyMap, +): { edits: AnnotationEdit[]; skipped: string[] } { + const edits: AnnotationEdit[] = []; + const skipped: string[] = []; + for (const v of model.variables) { + if (lines && (v.line < lines[0] || v.line > lines[1])) continue; + if (v.declaredType || v.initializer === null) continue; + let type: string | null = null; + const compiled = compilerTypes?.get(v.name); + if (compiled) type = ReferenceIndex.baseType(compiled); + else type = inferType(v.initializer, { ref, model, line: v.line, compilerTypes }); + if (!type || type === 'enum') { + skipped.push(v.name); + continue; + } + edits.push({ line: v.line, column: v.column, insert: `${type} ` }); + } + return { edits, skipped }; +} +``` + +- [ ] **Step 4: Run to verify it passes** + +Run: `npx vitest run tests/core/type-inference.test.ts` +Expected: PASS. Cases that depend on reference data (`input.source` returning `series float`, `label.new` returning `series label`) are covered through `baseType`; if a case fails because the scraped return type differs, fix the expectation only after checking `reference.json`. + +- [ ] **Step 5: Create `src/extension/commands/add-type-annotations.ts`** + +```ts +import * as vscode from 'vscode'; +import { loadReference } from '../core/reference'; +import { planTypeAnnotations } from '../core/type-inference'; +import { analyze } from '../vscode/document-cache'; + +export type CompilerTypesLookup = (uri: vscode.Uri) => ReadonlyMap | undefined; + +export function registerAddTypeAnnotations(context: vscode.ExtensionContext, compilerTypes: CompilerTypesLookup): void { + context.subscriptions.push( + vscode.commands.registerCommand('pinescript.addTypeAnnotations', async () => { + const editor = vscode.window.activeTextEditor; + if (!editor || editor.document.languageId !== 'pinescript') return; + const { model } = analyze(editor.document); + const range: [number, number] | null = editor.selection.isEmpty + ? null + : [editor.selection.start.line, editor.selection.end.line]; + const { edits, skipped } = planTypeAnnotations(model, loadReference(), range, compilerTypes(editor.document.uri)); + if (!edits.length) { + void vscode.window.showInformationMessage( + skipped.length + ? `No types could be inferred for: ${skipped.join(', ')}` + : 'Every declaration already has a type.', + ); + return; + } + await editor.edit((b) => { + for (const e of edits) b.insert(new vscode.Position(e.line, e.column), e.insert); + }); + const tail = skipped.length ? ` Skipped ${skipped.length}: ${skipped.join(', ')}.` : ''; + void vscode.window.showInformationMessage( + `Added ${edits.length} type annotation${edits.length === 1 ? '' : 's'}.${tail}`, + ); + }), + ); +} +``` + +- [ ] **Step 6: Contribute the command and menu in `package.json`** + +Inside `contributes` add (the other commands are appended in later tasks): + +```json +"commands": [ + { "command": "pinescript.addTypeAnnotations", "title": "Pine Script: Add Type Annotations", "enablement": "editorLangId == pinescript" } +], +"menus": { + "editor/context": [ + { "command": "pinescript.addTypeAnnotations", "when": "editorLangId == pinescript", "group": "1_pinescript@2" } + ] +} +``` + +- [ ] **Step 7: Register in `extension.ts`** + +Add `import { registerAddTypeAnnotations } from './commands/add-type-annotations';` and inside `activate`, after `registerProviders`: + +```ts +registerAddTypeAnnotations(context, () => undefined); // compiler types arrive in Task 13 +``` + +- [ ] **Step 8: Verify and commit** + +Run: `npm run typecheck && npm run build:extension && npm run test:core`. In the Extension Development Host, run the command on `consumer.pine`: `length`, `src`, `fast` gain types; `dc`/`up` are left alone. + +```bash +npm run format +git add -A +git commit -m "Infer declaration types and add the Add Type Annotations command" +``` + +--- + +### Task 10: Docstring generation, command and code action + +**Files:** + +- Create: `src/extension/core/docstring.ts`, `src/extension/commands/generate-docstring.ts`, `src/extension/providers/code-action.ts`, `tests/core/docstring.test.ts` +- Modify: `src/extension/extension.ts`, `package.json` + +**Interfaces:** + +- Consumes: `DeclSymbol`, `declarationAt`, `annotationsAbove`, `LineRange` (Task 5). +- Produces: + + ```ts + function annotationBlockRange(lines: string[], declLine: number): LineRange | null; // contiguous // block directly above, or null + function docstringLines(symbol: DeclSymbol, existing: string[], indent: string): string[]; // full replacement block, merged + function needsDocstring(symbol: DeclSymbol): boolean; + ``` + +- [ ] **Step 1: Write the failing test `tests/core/docstring.test.ts`** + +```ts +import { describe, expect, it } from 'vitest'; +import { buildModel } from '../../src/extension/core/document-model'; +import { annotationBlockRange, docstringLines, needsDocstring } from '../../src/extension/core/docstring'; + +describe('docstring', () => { + it('generates a function block', () => { + const m = buildModel('f(float a, b = 1) =>\n a + b\n'); + expect(docstringLines(m.functions[0]!, [], '')).toEqual([ + '//@function f', + '//@param a ', + '//@param b ', + '//@returns ', + ]); + }); + + it('omits @returns for void bodies', () => { + const m = buildModel('show(x) =>\n plot(x)\n'); + expect(docstringLines(m.functions[0]!, [], '')).toEqual(['//@function show', '//@param x ']); + }); + + it('merges with an existing block, keeping text and order', () => { + const src = '//@param b Second.\n//@function Adds.\nf(a, b) =>\n a + b\n'; + const lines = src.split('\n'); + const m = buildModel(src); + const range = annotationBlockRange(lines, 2); + expect(range).toEqual({ start: 0, end: 1 }); + expect(docstringLines(m.functions[0]!, lines.slice(0, 2), '')).toEqual([ + '//@function Adds.', + '//@param a ', + '//@param b Second.', + '//@returns ', + ]); + }); + + it('generates type and enum blocks with indentation', () => { + const m = buildModel('export type P\n float x\n int y = 0\nenum S\n a = "A"\n b\n'); + expect(docstringLines(m.types[0]!, [], '')).toEqual(['//@type P', '//@field x ', '//@field y ']); + expect(docstringLines(m.enums[0]!, [], ' ')).toEqual([' //@enum S', ' //@field a ', ' //@field b ']); + }); + + it('reports whether a symbol still needs docs', () => { + const documented = buildModel('//@function Adds.\n//@param a A.\n//@returns Sum.\nf(a) =>\n a\n'); + expect(needsDocstring(documented.functions[0]!)).toBe(false); + const partial = buildModel('//@function Adds.\nf(a) =>\n a\n'); + expect(needsDocstring(partial.functions[0]!)).toBe(true); + }); + + it('returns null when there is no block above', () => { + expect(annotationBlockRange(['x = 1', 'f(a) =>'], 1)).toBeNull(); + }); +}); +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `npx vitest run tests/core/docstring.test.ts` +Expected: FAIL, module not found. + +- [ ] **Step 3: Implement `src/extension/core/docstring.ts`** + +```ts +import type { DeclSymbol, FunctionSymbol, LineRange } from './document-model'; + +const VOID_CALLS = + /^(?:plot|plotshape|plotchar|plotarrow|plotcandle|plotbar|bgcolor|barcolor|fill|hline|alert|alertcondition|log\.\w+|runtime\.error|strategy\.(?:entry|exit|close|close_all|order|cancel|cancel_all|risk\.\w+)|label\.set_\w+|line\.set_\w+|box\.set_\w+|table\.(?:cell|set_\w+|clear|merge_cells)|array\.(?:push|set|unshift|clear|insert|fill|sort|reverse)|matrix\.(?:set|fill|add_row|add_col|remove_row|remove_col)|map\.(?:put|remove|clear))\s*\(/; + +export function annotationBlockRange(lines: string[], declLine: number): LineRange | null { + let start = declLine; + while (start > 0 && /^\s*\/\//.test(lines[start - 1]!)) start--; + if (start === declLine) return null; + // Only count the block if it contains at least one annotation; plain comments are left alone. + const block = lines.slice(start, declLine); + if (!block.some((l) => /^\s*\/\/\s*@\w+/.test(l))) return null; + return { start, end: declLine - 1 }; +} + +function tagOf(line: string): { tag: string; name: string | null } | null { + const m = line.match(/^\s*\/\/\s*@(\w+)\s*(\w+)?/); + if (!m) return null; + const named = m[1] === 'param' || m[1] === 'field'; + return { tag: m[1]!, name: named ? (m[2] ?? null) : null }; +} + +function bodyReturnsValue(fn: FunctionSymbol, lines?: string[]): boolean { + void lines; + return !fn.lastLine || !VOID_CALLS.test(fn.lastLine.trim()); +} + +export function docstringLines(symbol: DeclSymbol, existing: string[], indent: string): string[] { + const wanted: { tag: string; name: string | null }[] = []; + if (symbol.kind === 'function') { + wanted.push({ tag: 'function', name: null }); + for (const p of symbol.params) wanted.push({ tag: 'param', name: p.name }); + if (bodyReturnsValue(symbol)) wanted.push({ tag: 'returns', name: null }); + } else if (symbol.kind === 'type') { + wanted.push({ tag: 'type', name: null }); + for (const f of symbol.fields) wanted.push({ tag: 'field', name: f.name }); + } else { + wanted.push({ tag: 'enum', name: null }); + for (const m of symbol.members) wanted.push({ tag: 'field', name: m.name }); + } + + const remaining = existing.map((l) => ({ line: l, key: tagOf(l) })); + const out: string[] = []; + for (const w of wanted) { + const idx = remaining.findIndex((r) => r.key && r.key.tag === w.tag && r.key.name === w.name); + if (idx >= 0) { + out.push(indent + remaining[idx]!.line.trim()); + remaining.splice(idx, 1); + } else { + const label = w.tag === 'function' || w.tag === 'type' || w.tag === 'enum' ? symbol.name : w.name!; + out.push(`${indent}//@${w.tag} ${label}${w.tag === 'returns' ? '' : ' '}`.replace(/\s+$/, ' ')); + } + } + // Keep anything else the author wrote (descriptions, unknown tags, continuation lines) after the generated block. + for (const r of remaining) out.push(indent + r.line.trim()); + return out.map((l) => (l.endsWith('//@returns ') ? l.trimEnd() + ' ' : l)); +} + +export function needsDocstring(symbol: DeclSymbol): boolean { + if (symbol.kind === 'function') { + if (!symbol.docs.function && !symbol.docs.description) return true; + if (symbol.params.some((p) => !symbol.docs.params[p.name])) return true; + return bodyReturnsValue(symbol) && !symbol.docs.returns; + } + if (symbol.kind === 'type') return !symbol.docs.type || symbol.fields.some((f) => !symbol.docs.fields[f.name]); + return !symbol.docs.enum || symbol.members.some((m) => !symbol.docs.fields[m.name]); +} +``` + +`bodyReturnsValue` needs the last non-blank body line. Add `lastLine: string | null` to `FunctionSymbol` in `document-model.ts`: when constructing the function symbol set `lastLine` to the trimmed text of the last non-blank line in `range` after the header (or `null` when the body is empty). Update the Task 5 interface block mentally: `FunctionSymbol` gains `lastLine`. + +- [ ] **Step 4: Run to verify it passes** + +Run: `npx vitest run tests/core/docstring.test.ts tests/core/document-model.test.ts` +Expected: PASS. The generated `//@returns ` line keeps one trailing space so the cursor lands after it; the expected arrays above include that space. + +- [ ] **Step 5: Create `src/extension/commands/generate-docstring.ts`** + +```ts +import * as vscode from 'vscode'; +import { annotationBlockRange, docstringLines } from '../core/docstring'; +import { declarationAt, type DeclSymbol } from '../core/document-model'; +import { analyze } from '../vscode/document-cache'; + +export function docstringEdit(document: vscode.TextDocument, symbol: DeclSymbol): vscode.TextEdit { + const { lines } = analyze(document); + const indent = lines[symbol.line]!.match(/^\s*/)![0]; + const block = annotationBlockRange(lines, symbol.line); + const existing = block ? lines.slice(block.start, block.end + 1) : []; + const text = docstringLines(symbol, existing, indent).join('\n') + '\n'; + return block + ? vscode.TextEdit.replace(new vscode.Range(block.start, 0, block.end + 1, 0), text) + : vscode.TextEdit.insert(new vscode.Position(symbol.line, 0), text); +} + +export function registerGenerateDocstring(context: vscode.ExtensionContext): void { + context.subscriptions.push( + vscode.commands.registerCommand('pinescript.generateDocstring', async (line?: number) => { + const editor = vscode.window.activeTextEditor; + if (!editor || editor.document.languageId !== 'pinescript') return; + const { model } = analyze(editor.document); + const from = line ?? editor.selection.start.line; + const to = line ?? editor.selection.end.line; + const targets: DeclSymbol[] = []; + for (let l = from; l <= to; l++) { + const d = declarationAt(model, l); + if (d) targets.push(d); + } + if (!targets.length) { + // Cursor inside a body: use the enclosing declaration. + const enclosing = [...model.functions, ...model.types, ...model.enums].find( + (s) => s.range.start <= from && from <= s.range.end, + ); + if (enclosing) targets.push(enclosing); + } + if (!targets.length) { + void vscode.window.showInformationMessage('Place the cursor on a function, method, type or enum declaration.'); + return; + } + const edit = new vscode.WorkspaceEdit(); + for (const t of targets.sort((a, b) => b.line - a.line)) + edit.set(editor.document.uri, [docstringEdit(editor.document, t)]); + await vscode.workspace.applyEdit(edit); + }), + ); +} +``` + +Note: `edit.set` replaces previous edits for the same uri; collect all `TextEdit`s into one array and call `edit.set(uri, edits)` once. Write it that way. + +- [ ] **Step 6: Create `src/extension/providers/code-action.ts`** + +```ts +import * as vscode from 'vscode'; +import { needsDocstring } from '../core/docstring'; +import { declarationAt } from '../core/document-model'; +import { analyze } from '../vscode/document-cache'; + +export class PineCodeActionProvider implements vscode.CodeActionProvider { + static readonly metadata: vscode.CodeActionProviderMetadata = { + providedCodeActionKinds: [vscode.CodeActionKind.Refactor], + }; + + provideCodeActions(document: vscode.TextDocument, range: vscode.Range): vscode.CodeAction[] { + const { model } = analyze(document); + const decl = declarationAt(model, range.start.line); + if (!decl || !needsDocstring(decl)) return []; + const action = new vscode.CodeAction(`Generate docstring for ${decl.name}`, vscode.CodeActionKind.Refactor); + action.command = { command: 'pinescript.generateDocstring', title: 'Generate docstring', arguments: [decl.line] }; + return [action]; + } +} +``` + +- [ ] **Step 7: Contribute and register** + +`package.json` `contributes.commands` add: + +```json +{ + "command": "pinescript.generateDocstring", + "title": "Pine Script: Generate Docstring", + "enablement": "editorLangId == pinescript" +} +``` + +`contributes.menus["editor/context"]` add: + +```json +{ "command": "pinescript.generateDocstring", "when": "editorLangId == pinescript", "group": "1_pinescript@1" } +``` + +`extension.ts`: import `registerGenerateDocstring` and `PineCodeActionProvider`; in `activate` add: + +```ts +registerGenerateDocstring(context); +context.subscriptions.push( + vscode.languages.registerCodeActionsProvider(SELECTOR, new PineCodeActionProvider(), PineCodeActionProvider.metadata), +); +``` + +- [ ] **Step 8: Verify and commit** + +Run: `npm run typecheck && npm run build:extension && npm run test:core`. In the Extension Development Host, put the cursor on `internal(x) =>` in `library.pine`, use the lightbulb: a block with `//@function internal`, `//@param x `, `//@returns ` appears above. + +```bash +npm run format +git add -A +git commit -m "Generate and merge annotation docstrings from a command and a code action" +``` + +--- + +### Task 11: Templates, new-file commands, and Open Reference + +**Files:** + +- Create: `src/extension/core/templates.ts`, `src/extension/commands/new-file.ts`, `src/extension/commands/open-reference.ts`, `tests/core/templates.test.ts` +- Modify: `src/extension/extension.ts`, `package.json` + +**Interfaces:** + +- Produces: + + ```ts + type TemplateKind = 'indicator' | 'strategy' | 'library'; + function renderTemplate(kind: TemplateKind, opts: { title: string; date: string }): string; + ``` + +- [ ] **Step 1: Write the failing test `tests/core/templates.test.ts`** + +```ts +import { describe, expect, it } from 'vitest'; +import { renderTemplate } from '../../src/extension/core/templates'; + +describe('renderTemplate', () => { + it('renders an indicator with the title and date', () => { + const t = renderTemplate('indicator', { title: 'My Indicator', date: '2026-09-07' }); + expect(t.startsWith('//@version=6\n')).toBe(true); + expect(t).toContain('indicator("My Indicator"'); + expect(t).toContain('2026-09-07'); + expect(t).toContain('plot('); + }); + + it('renders a strategy with entries and exits', () => { + const t = renderTemplate('strategy', { title: 'S', date: '2026-09-07' }); + expect(t).toContain('strategy("S"'); + expect(t).toContain('strategy.entry('); + expect(t).toContain('strategy.close('); + }); + + it('renders a library with an exported function and docs', () => { + const t = renderTemplate('library', { title: 'Lib', date: '2026-09-07' }); + expect(t).toContain('library("Lib")'); + expect(t).toContain('//@function'); + expect(t).toContain('export '); + }); +}); +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `npx vitest run tests/core/templates.test.ts` +Expected: FAIL, module not found. + +- [ ] **Step 3: Implement `src/extension/core/templates.ts`** + +```ts +export type TemplateKind = 'indicator' | 'strategy' | 'library'; + +export function renderTemplate(kind: TemplateKind, opts: { title: string; date: string }): string { + const title = opts.title.replace(/"/g, '\\"'); + const header = `//@version=6\n// ${title}\n// Created ${opts.date}\n\n`; + switch (kind) { + case 'indicator': + return ( + header + + `indicator("${title}", overlay = true) + +// Inputs +lengthInput = input.int(20, "Length", minval = 1) +sourceInput = input.source(close, "Source") + +// Calculations +float ma = ta.sma(sourceInput, lengthInput) + +// Plots +plot(ma, "MA", color = color.blue, linewidth = 2) +` + ); + case 'strategy': + return ( + header + + `strategy("${title}", overlay = true, initial_capital = 10000, default_qty_type = strategy.percent_of_equity, default_qty_value = 10) + +// Inputs +fastInput = input.int(9, "Fast length", minval = 1) +slowInput = input.int(21, "Slow length", minval = 1) + +// Calculations +float fast = ta.ema(close, fastInput) +float slow = ta.ema(close, slowInput) +bool longCondition = ta.crossover(fast, slow) +bool exitCondition = ta.crossunder(fast, slow) + +// Orders +if longCondition + strategy.entry("Long", strategy.long) +if exitCondition + strategy.close("Long") + +// Plots +plot(fast, "Fast", color = color.teal) +plot(slow, "Slow", color = color.orange) +` + ); + case 'library': + return ( + header + + `//@description ${title}: describe what this library offers. +library("${title}") + +//@function Returns the arithmetic mean of two values. +//@param a First value. +//@param b Second value. +//@returns The mean of \`a\` and \`b\`. +export mean(float a, float b) => + (a + b) / 2 +` + ); + } +} +``` + +- [ ] **Step 4: Run to verify it passes** + +Run: `npx vitest run tests/core/templates.test.ts` +Expected: PASS (3 tests). + +- [ ] **Step 5: Create `src/extension/commands/new-file.ts`** + +```ts +import * as vscode from 'vscode'; +import { renderTemplate, type TemplateKind } from '../core/templates'; + +const TITLES: Record = { + indicator: 'My Indicator', + strategy: 'My Strategy', + library: 'MyLibrary', +}; + +export function registerNewFileCommands(context: vscode.ExtensionContext): void { + for (const kind of ['indicator', 'strategy', 'library'] as const) { + const id = `pinescript.new${kind[0]!.toUpperCase()}${kind.slice(1)}`; + context.subscriptions.push( + vscode.commands.registerCommand(id, async () => { + const title = await vscode.window.showInputBox({ + prompt: `${kind[0]!.toUpperCase()}${kind.slice(1)} title`, + value: TITLES[kind], + }); + if (title === undefined) return; + const content = renderTemplate(kind, { + title: title || TITLES[kind], + date: new Date().toISOString().slice(0, 10), + }); + const doc = await vscode.workspace.openTextDocument({ language: 'pinescript', content }); + await vscode.window.showTextDocument(doc); + }), + ); + } +} +``` + +- [ ] **Step 6: Create `src/extension/commands/open-reference.ts`** + +```ts +import * as vscode from 'vscode'; +import { REFERENCE_URL, loadReference } from '../core/reference'; +import { wordAt } from '../core/tokenizer'; + +export function registerOpenReference(context: vscode.ExtensionContext): void { + context.subscriptions.push( + vscode.commands.registerCommand('pinescript.openReference', async () => { + const editor = vscode.window.activeTextEditor; + let url = REFERENCE_URL; + if (editor && editor.document.languageId === 'pinescript') { + const line = editor.document.lineAt(editor.selection.active.line).text; + const word = wordAt(line, editor.selection.active.character); + const ref = loadReference(); + const candidates = word + ? [ + word.text, + ...word.text + .split('.') + .map((_, i, a) => a.slice(0, a.length - i - 1).join('.')) + .filter(Boolean), + ] + : []; + for (const c of candidates) { + const entry = ref.get(c); + if (entry) { + url = ref.url(entry); + break; + } + } + } + await vscode.env.openExternal(vscode.Uri.parse(url)); + }), + ); +} +``` + +- [ ] **Step 7: Contribute and register** + +`package.json` `contributes.commands` add: + +```json +{ "command": "pinescript.newIndicator", "title": "Pine Script: New Indicator" }, +{ "command": "pinescript.newStrategy", "title": "Pine Script: New Strategy" }, +{ "command": "pinescript.newLibrary", "title": "Pine Script: New Library" }, +{ "command": "pinescript.openReference", "title": "Pine Script: Open Reference" } +``` + +`contributes.menus["editor/context"]` add: + +```json +{ "command": "pinescript.openReference", "when": "editorLangId == pinescript", "group": "1_pinescript@3" } +``` + +`extension.ts`: import and call `registerNewFileCommands(context)` and `registerOpenReference(context)` in `activate`. + +- [ ] **Step 8: Verify and commit** + +Run: `npm run typecheck && npm run build:extension && npm run test:core`. In the Extension Development Host: command palette → "Pine Script: New Strategy" opens an untitled Pine document; right-click on `ta.sma` → Open Reference opens the anchor in the browser. + +```bash +npm run format +git add -A +git commit -m "Add file templates, new-file commands and Open Reference" +``` + +--- + +### Task 12: TradingView client, library parsing and the library index + +**Files:** + +- Create: `src/extension/core/pine-facade.ts`, `src/extension/providers/library-index.ts`, `tests/core/pine-facade.test.ts`, `tests/core/libraries.test.ts` +- Modify: `src/extension/core/libraries.ts`, `src/extension/extension.ts` + +**Interfaces:** + +- Consumes: `buildModel` (Task 5), `LibraryInfo`, `LibraryLookup` (Task 8), `getSettings` (Task 8), `log` (Task 1). +- Produces: + + ```ts + // pine-facade.ts + interface RemoteLibrary { + libId: string; + user: string; + lib: string; + version: string; + scriptIdPart: string; + docs: string; + } + interface RawIssue { + code?: string; + message: string; + ctx?: Record; + start?: { line: number; column: number }; + end?: { line: number; column: number }; + } + interface CompileResult { + success: boolean; + reason?: string; + errors: RawIssue[]; + warnings: RawIssue[]; + variables: { name: string; type: string }[]; + functions: { name: string; syntax: string; desc?: string; args: { name: string; type: string; info?: string }[] }[]; + } + interface FacadeOptions { + fetch?: typeof fetch; + now?: () => number; + timeoutMs?: number; + log?: (m: string) => void; + } + class PineFacade { + constructor(options?: FacadeOptions); + libList(prefix: string): Promise; + getScript(scriptIdPart: string, version: string): Promise; + translateLight(source: string): Promise; + readonly disabledUntil: number; // 0 when healthy + } + // libraries.ts + function parseLibrary( + text: string, + id: string, + source: 'local' | 'remote', + meta?: { owner?: string; version?: string; description?: string }, + ): LibraryInfo | null; + // library-index.ts + class LibraryIndex implements LibraryLookup { + constructor(facade: PineFacade, settings: () => Settings); + start(context): void; + } + ``` + +- [ ] **Step 1: Write the failing tests** + +`tests/core/pine-facade.test.ts`: + +```ts +import { describe, expect, it, vi } from 'vitest'; +import { PineFacade } from '../../src/extension/core/pine-facade'; + +const json = (body: unknown, status = 200) => + new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } }); + +describe('PineFacade', () => { + it('lists libraries and caches by prefix', async () => { + const fetch = vi.fn(async () => + json([ + { + libId: 'TradingView/ta/14', + user: 'TradingView', + lib: 'ta', + version: '14.0', + scriptIdPart: 'PUB;1', + docs: '', + }, + ]), + ); + const f = new PineFacade({ fetch: fetch as unknown as typeof globalThis.fetch }); + const a = await f.libList('TradingView/t'); + const b = await f.libList('TradingView/t'); + expect(a[0]?.libId).toBe('TradingView/ta/14'); + expect(b).toBe(a); + expect(fetch).toHaveBeenCalledTimes(1); + expect(String(fetch.mock.calls[0]![0])).toContain('lib_list/?lib_id_prefix=TradingView%2Ft&ignore_case=true'); + }); + + it('returns library source', async () => { + const fetch = vi.fn(async () => json({ source: '//@version=6\nlibrary("ta")\n' })); + const f = new PineFacade({ fetch: fetch as unknown as typeof globalThis.fetch }); + expect(await f.getScript('PUB;abc', '14.0')).toContain('library("ta")'); + expect(String(fetch.mock.calls[0]![0])).toContain('/get/PUB%3Babc/14.0?no_4xx=true'); + }); + + it('posts source to translate_light and normalizes the result', async () => { + const fetch = vi.fn(async () => + json({ + success: true, + result: { + errors2: [ + { + code: 'CE1', + message: 'Undeclared identifier "{identifier}"', + ctx: { identifier: 'x' }, + start: { line: 3, column: 6 }, + end: { line: 3, column: 7 }, + }, + ], + variables2: [{ docs: [{ name: 'a', type: 'series float' }] }], + functions2: [], + }, + }), + ); + const f = new PineFacade({ fetch: fetch as unknown as typeof globalThis.fetch }); + const r = await f.translateLight('//@version=6\nindicator("x")\nplot(x)'); + expect(r?.errors[0]?.code).toBe('CE1'); + expect(r?.variables).toEqual([{ name: 'a', type: 'series float' }]); + const init = fetch.mock.calls[0]![1] as RequestInit; + expect(init.method).toBe('POST'); + expect(String(init.body)).toContain('source='); + }); + + it('backs off after three failures', async () => { + let now = 1_000_000; + const fetch = vi.fn(async () => json({}, 500)); + const log = vi.fn(); + const f = new PineFacade({ fetch: fetch as unknown as typeof globalThis.fetch, now: () => now, log }); + for (let i = 0; i < 3; i++) expect(await f.libList(`p${i}`)).toEqual([]); + expect(f.disabledUntil).toBeGreaterThan(now); + await f.libList('p9'); + expect(fetch).toHaveBeenCalledTimes(3); + now += 5 * 60 * 1000 + 1; + await f.libList('p9'); + expect(fetch).toHaveBeenCalledTimes(4); + expect(log).toHaveBeenCalled(); + }); +}); +``` + +`tests/core/libraries.test.ts`: + +```ts +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; +import { parseLibrary } from '../../src/extension/core/libraries'; + +const fixture = (name: string) => readFileSync(new URL(`./fixtures/${name}`, import.meta.url), 'utf8'); + +describe('parseLibrary', () => { + it('keeps only exports and reads the description', () => { + const lib = parseLibrary(fixture('library.pine'), 'yankikucuk/MaHelpers/2', 'local', { + owner: 'yankikucuk', + version: '2', + })!; + expect(lib.title).toBe('MaHelpers'); + expect(lib.description).toBe('Helpers for moving averages.'); + expect(lib.functions.map((f) => f.name)).toEqual(['weighted']); + expect(lib.types.map((t) => t.name)).toEqual(['Level']); + expect(lib.enums.map((e) => e.name)).toEqual(['Side']); + }); + + it('returns null for non-libraries', () => { + expect(parseLibrary(fixture('consumer.pine'), 'x', 'local')).toBeNull(); + }); +}); +``` + +- [ ] **Step 2: Run to verify they fail** + +Run: `npx vitest run tests/core/pine-facade.test.ts tests/core/libraries.test.ts` +Expected: FAIL. + +- [ ] **Step 3: Implement `src/extension/core/pine-facade.ts`** + +```ts +export interface RemoteLibrary { + libId: string; + user: string; + lib: string; + version: string; + scriptIdPart: string; + docs: string; +} + +export interface RawIssue { + code?: string; + message: string; + ctx?: Record; + start?: { line: number; column: number }; + end?: { line: number; column: number }; +} + +export interface CompileResult { + success: boolean; + reason?: string; + errors: RawIssue[]; + warnings: RawIssue[]; + variables: { name: string; type: string }[]; + functions: { name: string; syntax: string; desc?: string; args: { name: string; type: string; info?: string }[] }[]; +} + +export interface FacadeOptions { + fetch?: typeof fetch; + now?: () => number; + timeoutMs?: number; + log?: (message: string) => void; +} + +const BASE = 'https://pine-facade.tradingview.com/pine-facade/'; +const LIB_TTL = 10 * 60 * 1000; +const BACKOFF = 5 * 60 * 1000; +const FAILURES_BEFORE_BACKOFF = 3; +const COMPILE_CACHE_SIZE = 100; + +export class PineFacade { + private readonly fetchFn: typeof fetch; + private readonly now: () => number; + private readonly timeoutMs: number; + private readonly log: (m: string) => void; + private failures = 0; + disabledUntil = 0; + private readonly libCache = new Map(); + private readonly scriptCache = new Map(); + private readonly compileCache = new Map(); + private readonly inflight = new Map>(); + + constructor(options: FacadeOptions = {}) { + this.fetchFn = options.fetch ?? globalThis.fetch; + this.now = options.now ?? Date.now; + this.timeoutMs = options.timeoutMs ?? 8000; + this.log = options.log ?? (() => {}); + } + + async libList(prefix: string): Promise { + const hit = this.libCache.get(prefix); + if (hit && this.now() - hit.at < LIB_TTL) return hit.value; + const data = await this.request( + 'GET', + `lib_list/?lib_id_prefix=${encodeURIComponent(prefix)}&ignore_case=true`, + ); + const value = Array.isArray(data) ? (data as RemoteLibrary[]).filter((l) => typeof l.libId === 'string') : []; + this.libCache.set(prefix, { at: this.now(), value }); + return value; + } + + async getScript(scriptIdPart: string, version: string): Promise { + const key = `${scriptIdPart}@${version}`; + if (this.scriptCache.has(key)) return this.scriptCache.get(key)!; + const data = await this.request<{ source?: string }>( + 'GET', + `get/${encodeURIComponent(scriptIdPart)}/${encodeURIComponent(version)}?no_4xx=true`, + ); + const source = typeof data?.source === 'string' ? data.source : null; + this.scriptCache.set(key, source); + return source; + } + + async translateLight(source: string): Promise { + const key = hash(source); + if (this.compileCache.has(key)) return this.compileCache.get(key)!; + const body = new URLSearchParams({ source }); + const data = await this.request<{ success?: boolean; reason?: string; result?: Record }>( + 'POST', + 'translate_light?user_name=Guest&pine_id=00000000-0000-0000-0000-000000000000', + body, + ); + const result = data ? normalize(data) : null; + this.compileCache.set(key, result); + if (this.compileCache.size > COMPILE_CACHE_SIZE) this.compileCache.delete(this.compileCache.keys().next().value!); + return result; + } + + private async request(method: 'GET' | 'POST', path: string, body?: URLSearchParams): Promise { + if (this.now() < this.disabledUntil) return null; + const key = `${method} ${path} ${body?.toString() ?? ''}`; + const existing = this.inflight.get(key); + if (existing) return existing as Promise; + const p = this.doRequest(method, path, body).finally(() => this.inflight.delete(key)); + this.inflight.set(key, p); + return p; + } + + private async doRequest(method: 'GET' | 'POST', path: string, body?: URLSearchParams): Promise { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), this.timeoutMs); + try { + const res = await this.fetchFn(BASE + path, { + method, + headers: { Accept: 'application/json', Referer: 'https://www.tradingview.com/' }, + body, + signal: controller.signal, + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + this.failures = 0; + return (await res.json()) as T; + } catch (err) { + this.failures++; + this.log( + `TradingView request failed (${method} ${path.split('?')[0]}): ${err instanceof Error ? err.message : String(err)}`, + ); + if (this.failures >= FAILURES_BEFORE_BACKOFF) { + this.disabledUntil = this.now() + BACKOFF; + this.failures = 0; + this.log('Remote features paused for five minutes after repeated failures.'); + } + return null; + } finally { + clearTimeout(timer); + } + } +} + +function normalize(data: { success?: boolean; reason?: string; result?: Record }): CompileResult { + const r = data.result ?? {}; + const docsOf = (key: string): unknown[] => { + const groups = r[key]; + return Array.isArray(groups) + ? groups.flatMap((g) => (Array.isArray((g as { docs?: unknown[] }).docs) ? (g as { docs: unknown[] }).docs : [])) + : []; + }; + return { + success: data.success !== false, + reason: data.reason, + errors: Array.isArray(r.errors2) ? (r.errors2 as RawIssue[]) : [], + warnings: Array.isArray(r.warnings2) ? (r.warnings2 as RawIssue[]) : [], + variables: docsOf('variables2') as CompileResult['variables'], + functions: docsOf('functions2') as CompileResult['functions'], + }; +} + +function hash(text: string): string { + let h = 2166136261; + for (let i = 0; i < text.length; i++) { + h ^= text.charCodeAt(i); + h = Math.imul(h, 16777619); + } + return (h >>> 0).toString(16) + ':' + text.length; +} +``` + +- [ ] **Step 4: Add `parseLibrary` to `src/extension/core/libraries.ts`** + +Append: + +```ts +import { buildModel } from './document-model'; + +export function parseLibrary( + text: string, + id: string, + source: 'local' | 'remote', + meta: { owner?: string; version?: string; description?: string } = {}, +): LibraryInfo | null { + const model = buildModel(text); + if (model.scriptKind !== 'library') return null; + const descriptionLine = text.split(/\r?\n/).find((l) => /^\/\/\s*@description\b/.test(l)); + return { + id, + title: model.libraryTitle ?? id, + owner: meta.owner ?? null, + version: meta.version ?? null, + description: + meta.description || (descriptionLine ? descriptionLine.replace(/^\/\/\s*@description\s*/, '').trim() : null), + functions: model.functions.filter((f) => f.isExport), + types: model.types.filter((t) => t.isExport), + enums: model.enums.filter((e) => e.isExport), + source, + }; +} +``` + +(Move the `import` to the top of the file with the existing type import.) + +- [ ] **Step 5: Create `src/extension/providers/library-index.ts`** + +```ts +import * as vscode from 'vscode'; +import type { ImportDecl } from '../core/document-model'; +import { parseLibrary, type LibraryInfo, type LibraryLookup } from '../core/libraries'; +import type { PineFacade } from '../core/pine-facade'; +import type { Settings } from '../vscode/settings'; + +export class LibraryIndex implements LibraryLookup { + private readonly localByUri = new Map(); + private readonly remote = new Map>(); + private watcher: vscode.FileSystemWatcher | undefined; + + constructor( + private readonly facade: PineFacade, + private readonly settings: () => Settings, + ) {} + + start(context: vscode.ExtensionContext): void { + void this.scan(); + this.watch(context); + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('pinescript.libraries.local.include')) { + this.localByUri.clear(); + void this.scan(); + this.watch(context); + } + }), + vscode.workspace.onDidSaveTextDocument((d) => { + if (d.languageId === 'pinescript') this.index(d.uri, d.getText()); + }), + ); + } + + local(): LibraryInfo[] { + return [...this.localByUri.values()]; + } + + async search(prefix: string): Promise { + if (!this.settings().librariesRemote || prefix.length < 2) return []; + const list = await this.facade.libList(prefix); + return list.map((l) => ({ + id: l.libId, + title: l.lib, + owner: l.user, + version: l.libId.split('/')[2] ?? l.version, + description: l.docs || null, + functions: [], + types: [], + enums: [], + source: 'remote' as const, + })); + } + + forImport(imp: ImportDecl): Promise { + const id = `${imp.owner}/${imp.name}/${imp.version}`; + const local = this.local().find((l) => l.title === imp.name || l.id === id); + if (local) return Promise.resolve(local); + if (!this.settings().librariesRemote) return Promise.resolve(null); + if (!this.remote.has(id)) this.remote.set(id, this.fetchRemote(imp, id)); + return this.remote.get(id)!; + } + + private async fetchRemote(imp: ImportDecl, id: string): Promise { + const list = await this.facade.libList(`${imp.owner}/${imp.name}/`); + const match = list.find((l) => l.libId === id) ?? list.find((l) => l.user === imp.owner && l.lib === imp.name); + if (!match) return null; + const source = await this.facade.getScript(match.scriptIdPart, match.version); + if (!source) return null; + const lib = parseLibrary(source, id, 'remote', { owner: imp.owner, version: imp.version, description: match.docs }); + if (!lib) this.remote.delete(id); + return lib; + } + + private async scan(): Promise { + if (!vscode.workspace.workspaceFolders?.length) return; + const files = await vscode.workspace.findFiles(this.settings().librariesInclude, '**/node_modules/**', 500); + for (const uri of files) { + if (uri.scheme !== 'file') continue; + try { + const bytes = await vscode.workspace.fs.readFile(uri); + this.index(uri, Buffer.from(bytes).toString('utf8')); + } catch { + // unreadable file: skip + } + } + } + + private index(uri: vscode.Uri, text: string): void { + if (!/\blibrary\s*\(/.test(text)) { + this.localByUri.delete(uri.toString()); + return; + } + const lib = parseLibrary(text, vscode.workspace.asRelativePath(uri), 'local'); + if (lib) this.localByUri.set(uri.toString(), lib); + else this.localByUri.delete(uri.toString()); + } + + private watch(context: vscode.ExtensionContext): void { + this.watcher?.dispose(); + this.watcher = vscode.workspace.createFileSystemWatcher(this.settings().librariesInclude); + this.watcher.onDidDelete((uri) => this.localByUri.delete(uri.toString())); + this.watcher.onDidCreate(async (uri) => + this.index(uri, Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')), + ); + context.subscriptions.push(this.watcher); + } +} +``` + +- [ ] **Step 6: Wire into `extension.ts`** + +Replace the `noLibraries` line in `activate` with: + +```ts +const facade = new PineFacade({ log }); +const libraries = new LibraryIndex(facade, getSettings); +libraries.start(context); +``` + +Keep `facade` in module scope (`let facade: PineFacade`) so Task 13 can reuse it. Remove the `noLibraries` import if unused (keep the export in `libraries.ts` for tests). + +- [ ] **Step 7: Run and verify** + +Run: `npx vitest run && npm run typecheck && npm run build:extension` +Expected: all green. In the Extension Development Host with `tests/core/fixtures` opened as the workspace: in `consumer.pine`, hover `import yankikucuk/MaHelpers/2 as ma` shows the local library card; `ma.` completes `weighted`, `Level`, `Side`; typing `import Tradi` lists TradingView libraries (needs network). + +- [ ] **Step 8: Commit** + +```bash +npm run format +git add -A +git commit -m "Index workspace libraries and look up published TradingView libraries" +``` + +--- + +### Task 13: Compiler diagnostics (opt-in) + +**Files:** + +- Create: `src/extension/core/diagnostics.ts`, `src/extension/providers/diagnostics-controller.ts`, `tests/core/diagnostics.test.ts` +- Modify: `src/extension/extension.ts`, `src/extension/commands/add-type-annotations.ts` wiring + +**Interfaces:** + +- Consumes: `PineFacade`, `CompileResult`, `RawIssue` (Task 12); `CompilerTypesLookup` (Task 9). +- Produces: + + ```ts + interface CompileDiagnostic { + line: number; + startCol: number; + endCol: number; + message: string; + severity: 'error' | 'warning'; + code: string | null; + } + function toDiagnostics(result: CompileResult, lineCount: number): CompileDiagnostic[]; + function compilerVariableTypes(result: CompileResult): Map; + class DiagnosticsController { + constructor(facade, settings); + start(context): void; + typesFor(uri): ReadonlyMap | undefined; + } + ``` + +- [ ] **Step 1: Write the failing test `tests/core/diagnostics.test.ts`** + +```ts +import { describe, expect, it } from 'vitest'; +import { compilerVariableTypes, toDiagnostics } from '../../src/extension/core/diagnostics'; + +describe('toDiagnostics', () => { + it('maps compiler issues to zero-based ranges with substituted messages', () => { + const d = toDiagnostics( + { + success: true, + errors: [ + { + code: 'CE10272', + ctx: { identifier: 'closee' }, + message: 'Undeclared identifier "{identifier}"', + start: { line: 3, column: 6 }, + end: { line: 3, column: 11 }, + }, + ], + warnings: [{ message: 'Unused variable', start: { line: 5, column: 1 }, end: { line: 5, column: 4 } }], + variables: [], + functions: [], + }, + 10, + ); + expect(d).toEqual([ + { + line: 2, + startCol: 5, + endCol: 10, + message: 'Undeclared identifier "closee"', + severity: 'error', + code: 'CE10272', + }, + { line: 4, startCol: 0, endCol: 3, message: 'Unused variable', severity: 'warning', code: null }, + ]); + }); + + it('reports a failure without positions on the first line', () => { + expect( + toDiagnostics( + { success: false, reason: 'Script too large', errors: [], warnings: [], variables: [], functions: [] }, + 3, + ), + ).toEqual([{ line: 0, startCol: 0, endCol: 0, message: 'Script too large', severity: 'error', code: null }]); + }); + + it('clamps lines beyond the document', () => { + const d = toDiagnostics( + { + success: true, + errors: [{ message: 'x', start: { line: 99, column: 1 }, end: { line: 99, column: 2 } }], + warnings: [], + variables: [], + functions: [], + }, + 3, + ); + expect(d[0]?.line).toBe(2); + }); + + it('collects variable types', () => { + const t = compilerVariableTypes({ + success: true, + errors: [], + warnings: [], + variables: [{ name: 'a', type: 'series float' }], + functions: [], + }); + expect(t.get('a')).toBe('series float'); + }); +}); +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `npx vitest run tests/core/diagnostics.test.ts` +Expected: FAIL. + +- [ ] **Step 3: Implement `src/extension/core/diagnostics.ts`** + +```ts +import type { CompileResult, RawIssue } from './pine-facade'; + +export interface CompileDiagnostic { + line: number; + startCol: number; + endCol: number; + message: string; + severity: 'error' | 'warning'; + code: string | null; +} + +function render(issue: RawIssue): string { + return issue.message.replace(/\{(\w+)\}/g, (_, key: string) => issue.ctx?.[key] ?? `{${key}}`); +} + +function one(issue: RawIssue, severity: 'error' | 'warning', lineCount: number): CompileDiagnostic { + const maxLine = Math.max(0, lineCount - 1); + const line = Math.min(maxLine, Math.max(0, (issue.start?.line ?? 1) - 1)); + const startCol = Math.max(0, (issue.start?.column ?? 1) - 1); + const endLine = Math.min(maxLine, Math.max(0, (issue.end?.line ?? issue.start?.line ?? 1) - 1)); + const endCol = endLine === line ? Math.max(startCol, (issue.end?.column ?? issue.start?.column ?? 1) - 1) : startCol; + return { line, startCol, endCol, message: render(issue), severity, code: issue.code ?? null }; +} + +export function toDiagnostics(result: CompileResult, lineCount: number): CompileDiagnostic[] { + const out = [ + ...result.errors.map((e) => one(e, 'error', lineCount)), + ...result.warnings.map((w) => one(w, 'warning', lineCount)), + ]; + if (!result.success && !out.length) { + out.push({ + line: 0, + startCol: 0, + endCol: 0, + message: result.reason ?? 'The TradingView compiler rejected the script.', + severity: 'error', + code: null, + }); + } + return out; +} + +export function compilerVariableTypes(result: CompileResult): Map { + return new Map(result.variables.map((v) => [v.name, v.type])); +} +``` + +- [ ] **Step 4: Implement `src/extension/providers/diagnostics-controller.ts`** + +```ts +import * as vscode from 'vscode'; +import { compilerVariableTypes, toDiagnostics } from '../core/diagnostics'; +import type { PineFacade } from '../core/pine-facade'; +import type { Settings } from '../vscode/settings'; + +const DEBOUNCE_MS = 600; + +export class DiagnosticsController { + private readonly collection = vscode.languages.createDiagnosticCollection('pinescript'); + private readonly timers = new Map>(); + private readonly types = new Map>(); + + constructor( + private readonly facade: PineFacade, + private readonly settings: () => Settings, + ) {} + + start(context: vscode.ExtensionContext): void { + context.subscriptions.push( + this.collection, + vscode.workspace.onDidOpenTextDocument((d) => this.schedule(d, 0)), + vscode.workspace.onDidChangeTextDocument((e) => this.schedule(e.document, DEBOUNCE_MS)), + vscode.workspace.onDidSaveTextDocument((d) => this.schedule(d, 0)), + vscode.workspace.onDidCloseTextDocument((d) => { + this.collection.delete(d.uri); + this.types.delete(d.uri.toString()); + }), + vscode.workspace.onDidChangeConfiguration((e) => { + if (!e.affectsConfiguration('pinescript.diagnostics.remote')) return; + if (this.settings().diagnosticsRemote) vscode.workspace.textDocuments.forEach((d) => this.schedule(d, 0)); + else this.collection.clear(); + }), + ); + vscode.workspace.textDocuments.forEach((d) => this.schedule(d, 0)); + } + + typesFor(uri: vscode.Uri): ReadonlyMap | undefined { + return this.types.get(uri.toString()); + } + + private schedule(document: vscode.TextDocument, delay: number): void { + if (document.languageId !== 'pinescript' || !this.settings().diagnosticsRemote) return; + const key = document.uri.toString(); + clearTimeout(this.timers.get(key)); + this.timers.set( + key, + setTimeout(() => void this.run(document), delay), + ); + } + + private async run(document: vscode.TextDocument): Promise { + const version = document.version; + const result = await this.facade.translateLight(document.getText()); + if (!result || document.isClosed || document.version !== version) return; + this.types.set(document.uri.toString(), compilerVariableTypes(result)); + this.collection.set( + document.uri, + toDiagnostics(result, document.lineCount).map((d) => { + const diag = new vscode.Diagnostic( + new vscode.Range(d.line, d.startCol, d.line, d.endCol), + d.message, + d.severity === 'error' ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning, + ); + diag.source = 'TradingView compiler'; + if (d.code) diag.code = d.code; + return diag; + }), + ); + } +} +``` + +- [ ] **Step 5: Wire into `extension.ts`** + +After creating `facade`: + +```ts +const diagnostics = new DiagnosticsController(facade, getSettings); +diagnostics.start(context); +``` + +and change the type-annotation registration to `registerAddTypeAnnotations(context, (uri) => diagnostics.typesFor(uri));`. + +- [ ] **Step 6: Verify and commit** + +Run: `npx vitest run && npm run typecheck && npm run build:extension`. In the Extension Development Host set `"pinescript.diagnostics.remote": true`, type `plot(closee)`: a red squiggle with "Undeclared identifier "closee"" appears within a second; turn the setting off: it disappears. + +```bash +npm run format +git add -A +git commit -m "Show TradingView compiler diagnostics when remote diagnostics are enabled" +``` + +--- + +### Task 14: Pine Dark and Pine Light themes + +**Files:** + +- Create: `themes/pine-dark-color-theme.json`, `themes/pine-light-color-theme.json`, `scripts/check-themes.mjs` +- Modify: `package.json` (`contributes.themes`), `scripts/build-grammar.mjs` (call the theme check in `--check` mode) + +**Interfaces:** + +- Produces: two theme files whose `tokenColors` cover every scope emitted by the grammar (checked by `scripts/check-themes.mjs`). + +- [ ] **Step 1: Write the theme coverage check `scripts/check-themes.mjs`** + +```js +// Fails when a scope emitted by the grammar has no rule in a theme. +import { readFileSync } from 'node:fs'; + +const grammar = JSON.parse(readFileSync('syntaxes/pinescript.tmLanguage.json', 'utf8')); +const scopes = new Set(); +JSON.stringify(grammar, (key, value) => { + if ((key === 'name' || key === 'contentName') && typeof value === 'string') + value.split(' ').forEach((s) => scopes.add(s)); + return value; +}); +scopes.delete('Pine'); +scopes.delete('Script'); + +let failed = false; +for (const file of ['themes/pine-dark-color-theme.json', 'themes/pine-light-color-theme.json']) { + const theme = JSON.parse(readFileSync(file, 'utf8')); + const rules = theme.tokenColors.flatMap((r) => (Array.isArray(r.scope) ? r.scope : [r.scope])); + const covered = (scope) => rules.some((r) => scope === r || scope.startsWith(r + '.')); + const missing = [...scopes].filter((s) => !covered(s)); + if (missing.length) { + failed = true; + console.error(`${file} has no rule for:\n ${missing.join('\n ')}`); + } +} +if (failed) process.exit(1); +console.log('Themes cover every grammar scope.'); +``` + +Add `"check:themes": "node scripts/check-themes.mjs"` to `package.json` scripts and append `&& npm run check:themes` to the `test` script after `build:grammar:check`. + +- [ ] **Step 2: Create `themes/pine-dark-color-theme.json`** + +```json +{ + "$schema": "vscode://schemas/color-theme", + "name": "Pine Dark", + "type": "dark", + "semanticHighlighting": false, + "colors": { + "editor.background": "#131722", + "editor.foreground": "#d1d4dc", + "editor.lineHighlightBackground": "#1e222d", + "editor.selectionBackground": "#2962ff55", + "editor.inactiveSelectionBackground": "#2962ff33", + "editor.selectionHighlightBackground": "#2962ff22", + "editor.wordHighlightBackground": "#2962ff33", + "editor.findMatchBackground": "#ff980066", + "editor.findMatchHighlightBackground": "#ff980033", + "editorCursor.foreground": "#d1d4dc", + "editorLineNumber.foreground": "#50535e", + "editorLineNumber.activeForeground": "#b2b5be", + "editorIndentGuide.background1": "#2a2e39", + "editorIndentGuide.activeBackground1": "#434651", + "editorWhitespace.foreground": "#2a2e39", + "editorBracketMatch.background": "#2962ff33", + "editorBracketMatch.border": "#2962ff", + "editorGutter.background": "#131722", + "editorWidget.background": "#1e222d", + "editorWidget.border": "#2a2e39", + "editorSuggestWidget.background": "#1e222d", + "editorSuggestWidget.selectedBackground": "#2962ff44", + "editorHoverWidget.background": "#1e222d", + "editorHoverWidget.border": "#2a2e39", + "editorError.foreground": "#f23645", + "editorWarning.foreground": "#ff9800", + "editorInfo.foreground": "#2962ff", + "sideBar.background": "#131722", + "sideBar.foreground": "#b2b5be", + "sideBar.border": "#2a2e39", + "sideBarSectionHeader.background": "#1e222d", + "activityBar.background": "#131722", + "activityBar.foreground": "#d1d4dc", + "activityBar.inactiveForeground": "#787b86", + "activityBarBadge.background": "#2962ff", + "activityBarBadge.foreground": "#ffffff", + "statusBar.background": "#1e222d", + "statusBar.foreground": "#b2b5be", + "statusBar.noFolderBackground": "#1e222d", + "statusBar.debuggingBackground": "#2962ff", + "titleBar.activeBackground": "#131722", + "titleBar.activeForeground": "#d1d4dc", + "titleBar.inactiveBackground": "#131722", + "tab.activeBackground": "#131722", + "tab.inactiveBackground": "#1e222d", + "tab.activeForeground": "#d1d4dc", + "tab.inactiveForeground": "#787b86", + "tab.border": "#2a2e39", + "tab.activeBorderTop": "#2962ff", + "editorGroupHeader.tabsBackground": "#1e222d", + "panel.background": "#131722", + "panel.border": "#2a2e39", + "input.background": "#1e222d", + "input.border": "#2a2e39", + "input.foreground": "#d1d4dc", + "dropdown.background": "#1e222d", + "list.activeSelectionBackground": "#2962ff44", + "list.hoverBackground": "#1e222d", + "list.inactiveSelectionBackground": "#2a2e39", + "focusBorder": "#2962ff", + "button.background": "#2962ff", + "button.foreground": "#ffffff", + "badge.background": "#2962ff", + "badge.foreground": "#ffffff", + "scrollbarSlider.background": "#434651aa", + "scrollbarSlider.hoverBackground": "#50535eaa", + "peekView.border": "#2962ff", + "terminal.background": "#131722", + "terminal.foreground": "#d1d4dc" + }, + "tokenColors": [ + { + "name": "Comments", + "scope": ["comment", "punctuation.definition.comment"], + "settings": { "foreground": "#5d606b", "fontStyle": "italic" } + }, + { + "name": "Annotations", + "scope": ["storage.type.annotation", "storage.type.annotation.version"], + "settings": { "foreground": "#787b86", "fontStyle": "bold italic" } + }, + { "name": "Raw text in comments", "scope": "markup.inline.raw", "settings": { "foreground": "#9598a1" } }, + { + "name": "Strings", + "scope": ["string", "punctuation.definition.string"], + "settings": { "foreground": "#4caf50" } + }, + { "name": "String escapes", "scope": "constant.character.escape", "settings": { "foreground": "#81c784" } }, + { "name": "Format placeholders", "scope": "constant.other.placeholder", "settings": { "foreground": "#ffb74d" } }, + { + "name": "Numbers", + "scope": ["constant.numeric", "constant.numeric.integer", "constant.numeric.float"], + "settings": { "foreground": "#ff9800" } + }, + { + "name": "Hex colors", + "scope": "constant.other.color.hex", + "settings": { "foreground": "#ff9800", "fontStyle": "underline" } + }, + { + "name": "Language constants (true, false, na)", + "scope": "constant.language", + "settings": { "foreground": "#ff5252" } + }, + { + "name": "Control keywords", + "scope": ["keyword.control", "keyword.control.loop", "keyword.control.import", "keyword.control.import.as"], + "settings": { "foreground": "#c792ea" } + }, + { + "name": "Declarations (type, enum, method, export, var, varip)", + "scope": [ + "storage.type.declaration", + "storage.type.declaration.enum", + "storage.modifier", + "storage.modifier.export", + "storage.modifier.method" + ], + "settings": { "foreground": "#c792ea", "fontStyle": "italic" } + }, + { + "name": "Type qualifiers", + "scope": "storage.modifier.qualifier", + "settings": { "foreground": "#b39ddb", "fontStyle": "italic" } + }, + { + "name": "Word operators (and, or, not)", + "scope": "keyword.operator.logical", + "settings": { "foreground": "#c792ea" } + }, + { + "name": "Operators", + "scope": [ + "keyword.operator", + "keyword.operator.arithmetic", + "keyword.operator.assignment", + "keyword.operator.comparison", + "keyword.operator.ternary" + ], + "settings": { "foreground": "#89ddff" } + }, + { + "name": "Arrow", + "scope": "keyword.operator.arrow", + "settings": { "foreground": "#89ddff", "fontStyle": "bold" } + }, + { + "name": "Types", + "scope": ["support.type", "entity.name.type", "entity.name.type.enum", "meta.generic"], + "settings": { "foreground": "#ffcb6b" } + }, + { "name": "Built-in namespaces", "scope": "support.class", "settings": { "foreground": "#82aaff" } }, + { + "name": "Built-in functions", + "scope": ["support.function", "support.function.constructor"], + "settings": { "foreground": "#2196f3" } + }, + { "name": "Built-in variables", "scope": "support.variable", "settings": { "foreground": "#26a69a" } }, + { "name": "Built-in constants", "scope": "support.constant", "settings": { "foreground": "#f78c6c" } }, + { + "name": "User functions and calls", + "scope": ["entity.name.function", "entity.name.function.call", "entity.name.function.member"], + "settings": { "foreground": "#82b1ff" } + }, + { + "name": "Import namespaces", + "scope": ["entity.name.namespace.import", "entity.name.namespace.alias"], + "settings": { "foreground": "#ffcb6b" } + }, + { + "name": "Parameters", + "scope": "variable.parameter", + "settings": { "foreground": "#eeffff", "fontStyle": "italic" } + }, + { + "name": "Variables", + "scope": ["variable.other", "variable.other.assignment", "variable.other.member"], + "settings": { "foreground": "#d1d4dc" } + }, + { + "name": "Punctuation", + "scope": [ + "punctuation", + "punctuation.accessor", + "punctuation.separator.comma", + "punctuation.section.parens.begin", + "punctuation.section.parens.end", + "punctuation.section.brackets.begin", + "punctuation.section.brackets.end", + "punctuation.definition.generic.begin", + "punctuation.definition.generic.end", + "punctuation.definition.tuple.begin", + "punctuation.definition.tuple.end", + "meta.parens", + "meta.tuple.destructuring" + ], + "settings": { "foreground": "#9598a1" } + } + ] +} +``` + +- [ ] **Step 3: Create `themes/pine-light-color-theme.json`** + +Same structure with `"name": "Pine Light"`, `"type": "light"`, and these palettes: + +```json +"colors": { + "editor.background": "#ffffff", + "editor.foreground": "#131722", + "editor.lineHighlightBackground": "#f0f3fa", + "editor.selectionBackground": "#2962ff33", + "editor.inactiveSelectionBackground": "#2962ff1a", + "editor.selectionHighlightBackground": "#2962ff14", + "editor.wordHighlightBackground": "#2962ff22", + "editor.findMatchBackground": "#ff980066", + "editor.findMatchHighlightBackground": "#ff980033", + "editorCursor.foreground": "#131722", + "editorLineNumber.foreground": "#b2b5be", + "editorLineNumber.activeForeground": "#434651", + "editorIndentGuide.background1": "#e0e3eb", + "editorIndentGuide.activeBackground1": "#b2b5be", + "editorWhitespace.foreground": "#e0e3eb", + "editorBracketMatch.background": "#2962ff22", + "editorBracketMatch.border": "#2962ff", + "editorGutter.background": "#ffffff", + "editorWidget.background": "#f0f3fa", + "editorWidget.border": "#e0e3eb", + "editorSuggestWidget.background": "#ffffff", + "editorSuggestWidget.selectedBackground": "#2962ff22", + "editorHoverWidget.background": "#ffffff", + "editorHoverWidget.border": "#e0e3eb", + "editorError.foreground": "#f23645", + "editorWarning.foreground": "#ff9800", + "editorInfo.foreground": "#2962ff", + "sideBar.background": "#f8f9fd", + "sideBar.foreground": "#434651", + "sideBar.border": "#e0e3eb", + "sideBarSectionHeader.background": "#f0f3fa", + "activityBar.background": "#f0f3fa", + "activityBar.foreground": "#131722", + "activityBar.inactiveForeground": "#787b86", + "activityBarBadge.background": "#2962ff", + "activityBarBadge.foreground": "#ffffff", + "statusBar.background": "#f0f3fa", + "statusBar.foreground": "#434651", + "statusBar.noFolderBackground": "#f0f3fa", + "statusBar.debuggingBackground": "#2962ff", + "titleBar.activeBackground": "#f8f9fd", + "titleBar.activeForeground": "#131722", + "titleBar.inactiveBackground": "#f8f9fd", + "tab.activeBackground": "#ffffff", + "tab.inactiveBackground": "#f0f3fa", + "tab.activeForeground": "#131722", + "tab.inactiveForeground": "#787b86", + "tab.border": "#e0e3eb", + "tab.activeBorderTop": "#2962ff", + "editorGroupHeader.tabsBackground": "#f0f3fa", + "panel.background": "#ffffff", + "panel.border": "#e0e3eb", + "input.background": "#ffffff", + "input.border": "#e0e3eb", + "input.foreground": "#131722", + "dropdown.background": "#ffffff", + "list.activeSelectionBackground": "#2962ff22", + "list.hoverBackground": "#f0f3fa", + "list.inactiveSelectionBackground": "#e0e3eb", + "focusBorder": "#2962ff", + "button.background": "#2962ff", + "button.foreground": "#ffffff", + "badge.background": "#2962ff", + "badge.foreground": "#ffffff", + "scrollbarSlider.background": "#b2b5be80", + "scrollbarSlider.hoverBackground": "#787b8680", + "peekView.border": "#2962ff", + "terminal.background": "#ffffff", + "terminal.foreground": "#131722" +} +``` + +and the same `tokenColors` list with these foregrounds: comments `#9598a1`; annotations `#787b86`; raw `#5d606b`; strings `#089981`; escapes `#0b7a5b`; placeholders `#b26a00`; numbers and hex `#e65100`; language constants `#d32f2f`; control keywords, declarations, logical operators `#7e57c2`; qualifiers `#9575cd`; operators and arrow `#0277bd`; types and import namespaces `#b26a00`; namespaces `#1565c0`; built-in functions `#2962ff`; built-in variables `#00897b`; built-in constants `#c2410c`; user functions `#1e88e5`; parameters `#37474f`; variables `#131722`; punctuation `#5d606b`. Font styles unchanged. + +- [ ] **Step 4: Contribute themes** + +`package.json` `contributes.themes`: + +```json +"themes": [ + { "label": "Pine Dark", "uiTheme": "vs-dark", "path": "./themes/pine-dark-color-theme.json" }, + { "label": "Pine Light", "uiTheme": "vs", "path": "./themes/pine-light-color-theme.json" } +] +``` + +- [ ] **Step 5: Verify** + +Run: `npm run check:themes` +Expected: `Themes cover every grammar scope.` Then in the Extension Development Host switch to Pine Dark and Pine Light with `tests/snapshots/strategy-v6.pine` open and check that comments, annotations, strings, numbers, built-ins per category, keywords and user functions are visibly distinct in both. + +- [ ] **Step 6: Commit** + +```bash +npm run format +git add -A +git commit -m "Add Pine Dark and Pine Light color themes with a scope coverage check" +``` + +--- + +### Task 15: Documentation, metadata, CI and the 3.0.0 release + +**Files:** + +- Modify: `README.md`, `CHANGELOG.md`, `.github/CONTRIBUTING.md`, `.github/workflows/ci.yml`, `.github/workflows/release.yml`, `package.json` + +- [ ] **Step 1: Rewrite `README.md`** + +Keep the header, badges and the screenshot. Replace the `## Features` section and everything after it with: + +```markdown +## Features + +- **Completion.** Built-in functions, variables and constants from every v6 namespace, keywords, types, your own functions, types, enums and variables, named arguments inside calls, `//@` annotations, and `import` paths. Function items insert a call and open parameter hints. +- **Hover documentation.** Signature, description, parameters, return value and a link to the reference entry for built-ins; declaration and `//@` docs for your own symbols; a library card on `import` lines. +- **Signature help.** Overloads and the active parameter, including named arguments. +- **Outline.** Functions, methods, types with fields, enums with members and top-level variables in the Outline view and breadcrumbs. +- **Libraries.** Workspace files that call `library()` are indexed: their exports complete after the import alias and show up on hover. With `pinescript.libraries.remote` on, `import` completion also lists published TradingView libraries and hover shows their exports. +- **Compiler diagnostics (opt-in).** Set `pinescript.diagnostics.remote` to `true` to send the document to the TradingView compiler and see its errors and warnings inline. +- **Commands.** New Indicator / Strategy / Library from a template, Generate Docstring (also a lightbulb on declarations), Add Type Annotations, Open Reference for the built-in under the cursor. +- **Themes.** Pine Dark and Pine Light, tuned for every scope the grammar produces. +- **Highlighting, snippets and editor support** as before: the full v6 vocabulary, annotations, triple-quoted strings, format placeholders, hex colors, folding, auto-closing pairs and four-space indentation for `.pine` files. + +Files ending in `.pine` or `.pinescript`, or starting with `//@version=`, are recognized automatically. + +### Pine Script v6 compatibility + +The grammar and the documentation data are generated from the v6 reference, so v6 behaviors such as dynamic `request.*()` calls with `series string` arguments, short-circuit `and`/`or`, point-based text sizes, `text.format_bold` / `text.format_italic`, order trimming and negative array indices are covered wherever they have syntax to highlight or document. They are compiler behaviors; the extension does not emulate them. + +## Commands + +| Command | What it does | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Pine Script: New Indicator | Opens an untitled document from the indicator template | +| Pine Script: New Strategy | Opens an untitled document from the strategy template | +| Pine Script: New Library | Opens an untitled document from the library template | +| Pine Script: Generate Docstring | Inserts or completes `//@function`, `//@param`, `//@returns`, `//@type`, `//@field`, `//@enum` for the declaration at the cursor | +| Pine Script: Add Type Annotations | Prefixes untyped declarations with their inferred type in the selection or the whole file | +| Pine Script: Open Reference | Opens the v6 reference at the built-in under the cursor | + +## Settings + +| Setting | Default | Meaning | +| ------------------------------------ | ----------- | ---------------------------------------------------------------------------- | +| `pinescript.completion.enabled` | `true` | Completion provider | +| `pinescript.hover.enabled` | `true` | Hover provider | +| `pinescript.signatureHelp.enabled` | `true` | Parameter hints | +| `pinescript.libraries.local.include` | `**/*.pine` | Glob for workspace library discovery | +| `pinescript.libraries.remote` | `true` | Look up published libraries on TradingView for `import` completion and hover | +| `pinescript.diagnostics.remote` | `false` | Send the document to the TradingView compiler for diagnostics | + +## Privacy + +Everything works offline. Two features talk to TradingView, both through undocumented endpoints that may change: + +- With `pinescript.libraries.remote` on, the prefix you type after `import` and the ids of imported libraries are sent to fetch library metadata and source. +- With `pinescript.diagnostics.remote` on, the **full text** of each open Pine document is sent to the compiler on open, on save and 600 ms after you stop typing. + +Nothing else leaves your machine. When a request fails, the feature is paused for five minutes and the reason is written to the "Pine Script" output channel. + +## Recommended companions + +- [vscode-icons](https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons) for a `.pine` file icon. + +## How it works +``` + +src/ +grammar.mjs grammar rules, expressed as data +data/ +functions.json built-in functions, grouped by namespace (grammar source) +variables.json built-in variables, grouped by namespace (grammar source) +constants.json built-in constants, grouped by namespace (grammar source) +annotations.json compiler annotations +reference.json full v6 documentation, generated by scripts/scrape-reference.mjs +extension/ +core/ pure TypeScript: tokenizer, document model, completion context, +type inference, docstrings, templates, TradingView client +providers/ VS Code adapters: completion, hover, signature help, symbols, +code actions, diagnostics, library index +commands/ command implementations +scripts/ +build-grammar.mjs compiles src/ into syntaxes/pinescript.tmLanguage.json and +checks it against reference.json +build-extension.mjs bundles src/extension into dist/extension.cjs with esbuild +scrape-reference.mjs regenerates src/data/reference.json (maintainers, needs network) +check-themes.mjs verifies both themes cover every grammar scope +themes/ Pine Dark and Pine Light +tests/ +core/ vitest suites for src/extension/core +unit/ scope assertions (vscode-tmgrammar-test) +snapshots/ full-file snapshots (vscode-tmgrammar-snap) + +```` + +`syntaxes/pinescript.tmLanguage.json`, `src/data/reference.json` and `dist/` are generated. Do not edit them by hand. + +## Development + +```sh +git clone https://github.com/yankikucuk/pine-script-syntax-highlighter.git +cd pine-script-syntax-highlighter +npm install +npm run build # grammar + extension bundle +npm run watch # rebuild the bundle on change +npm test # grammar check, type check, core tests, grammar tests +npm run package # build the .vsix +```` + +Press F5 in VS Code to launch an Extension Development Host with the extension loaded. Use **Developer: Inspect Editor Tokens and Scopes** to see which scope a token receives. + +See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the pull request checklist and [CHANGELOG.md](CHANGELOG.md) for release history. + +## License + +[MIT](LICENSE) © Yankı Küçük + +Pine Script® is a registered trademark of TradingView, Inc. This project is not affiliated with TradingView. + +```` + +- [ ] **Step 2: Update `CHANGELOG.md`** + +Add above `[2.0.1]`: + +```markdown +## [3.0.0] - 2026-09-07 + +### Added + +- Completion for built-ins, keywords, types, user symbols, named arguments, annotations and import paths, with documentation and signatures. +- Hover documentation for built-ins, user functions, types, enums, variables and imports. +- Signature help with overloads and named-argument awareness. +- Document outline (functions, methods, types, enums, top-level variables). +- Workspace library indexing and published TradingView library lookup for `import` completion and hover. +- Opt-in diagnostics from the TradingView compiler (`pinescript.diagnostics.remote`). +- Commands: New Indicator, New Strategy, New Library, Generate Docstring (with a code action), Add Type Annotations, Open Reference. +- Pine Dark and Pine Light color themes. +- `src/data/reference.json`, generated from the v6 reference, and a build check that keeps it in step with the grammar data. + +### Changed + +- Minimum VS Code version is 1.96. +- The package now ships a bundled extension entry point (`dist/extension.cjs`) built with esbuild. +- `npm run build` now builds both the grammar and the extension; grammar-only scripts are `build:grammar` and `build:grammar:check`. +```` + +Add the compare link `[3.0.0]: https://github.com/yankikucuk/pine-script-syntax-highlighter/compare/v2.0.1...v3.0.0` and point `[Unreleased]` at `v3.0.0...HEAD`. + +- [ ] **Step 3: Update `.github/CONTRIBUTING.md`** + +Add sections: + +````markdown +## Layout + +- `src/grammar.mjs` and `src/data/*.json` produce the grammar. `src/data/reference.json` is scraped, not edited. +- `src/extension/core` holds all logic and never imports `vscode`; put tests for it in `tests/core`. +- `src/extension/providers` and `src/extension/commands` only translate between VS Code and `core`. + +## Working on the extension + +```sh +npm run watch # rebuild dist/extension.cjs on change +``` +```` + +Press F5, then reload the Extension Development Host after each rebuild. + +## Refreshing the reference data + +```sh +npm install --no-save playwright-core +npx playwright-core install chromium +npm run scrape:reference +npm run build:grammar:check # lists built-ins to add to or remove from src/data/*.json +``` + +## Manual checklist before a release + +- Completion after `ta.`, inside `plot(`, after `//@`, after `import ` +- Hover on `close`, `ta.sma`, a user function, an import line +- Signature help through all parameters of `plot(` +- Outline shows functions, types, enums, variables +- Generate Docstring on a function, a type and an enum +- Add Type Annotations on the strategy snapshot fixture +- Both themes on `tests/snapshots/strategy-v6.pine` +- With `pinescript.diagnostics.remote` on: an undeclared identifier is underlined + +```` + +Update the pull request checklist item about tests to mention `tests/core` for extension logic. + +- [ ] **Step 4: Finalize CI and release workflows** + +`ci.yml` job steps in order: checkout, setup-node (22), `npm ci`, `npm run format:check`, `npm run build:grammar:check`, `npm run check:themes`, `npm run typecheck`, `npm run test:core`, `npm run test:unit`, `npm run test:snap`, `npm run build:extension`, `npx vsce package`, verify bundle in package, upload artifact. `release.yml`: same build steps before `vsce package`, then the existing tag/version check, GitHub release and gated publish. + +- [ ] **Step 5: Bump the version and verify the package** + +```bash +npm version 3.0.0 --no-git-tag-version +npm run format +npm test +npm run build:extension +npx vsce package +unzip -l pine-script-syntax-highlighter-3.0.0.vsix +```` + +Expected in the listing: `extension/dist/extension.cjs`, `extension/themes/*.json`, `extension/syntaxes/pinescript.tmLanguage.json`, `extension/snippets/*`, `extension/language-configuration.json`, `extension/images/*`, `extension/README.md`, `extension/CHANGELOG.md`, `extension/LICENSE`, and nothing under `extension/src`, `extension/tests`, `extension/scripts` or `extension/docs`. Install the vsix locally (`code --install-extension pine-script-syntax-highlighter-3.0.0.vsix`) and run the manual checklist from CONTRIBUTING. + +- [ ] **Step 6: Scan for attribution** + +Search the whole tree (excluding `node_modules`, `.git`, `dist` and the lockfile) for any mention of tools or assistants in commits, comments, docs or metadata, and remove every hit before committing. The reference JSON is generated text and is excluded from the scan. + +- [ ] **Step 7: Commit, tag, push** + +```bash +git add -A +git commit -m "Release 3.0.0: language features, themes and compiler diagnostics" +git tag -a v3.0.0 -m "3.0.0" +git push origin master --tags +``` + +Watch the Release workflow (`gh run watch`), then confirm the Marketplace shows 3.0.0: + +```bash +curl -s -X POST https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery -H 'Content-Type: application/json' -H 'Accept: application/json;api-version=7.1-preview.1' -d '{"filters":[{"criteria":[{"filterType":7,"value":"ex-codes.pine-script-syntax-highlighter"}]}],"flags":103}' | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).results[0].extensions[0].versions[0].version))' +``` + +--- + +## Self-review notes + +- Spec §4.3 consistency check → Task 2. §5.1–5.11 → Tasks 4, 5, 6, 6, 7, 9, 10, 12, 12, 13, 11. §6.1–6.8 → Tasks 8, 8, 8, 8, 13, 10, 9/10/11, 8. §7 → Task 14. §8 → each task's tests plus the CONTRIBUTING checklist in Task 15. §9 → Task 15. §10 privacy/robustness → Tasks 12, 13, 15 (README). +- `FunctionSymbol.lastLine` is introduced in Task 10 and must be added to the Task 5 implementation when Task 10 runs; Task 5's tests do not assert it. +- `LibraryLookup`/`LibraryInfo` are defined in Task 8 and implemented in Task 12; Task 8 wires `noLibraries` so the extension runs in between. +- `CompilerTypesLookup` from Task 9 is fed by `DiagnosticsController.typesFor` in Task 13. diff --git a/docs/specs/2026-09-07-language-features-design.md b/docs/specs/2026-09-07-language-features-design.md new file mode 100644 index 0000000..354e955 --- /dev/null +++ b/docs/specs/2026-09-07-language-features-design.md @@ -0,0 +1,482 @@ +# Language features for Pine Script v6 (3.0.0) + +Date: 2026-09-07 +Status: approved design, pending implementation plan + +## 1. Goal + +Turn the extension from a grammar-only package into a full language extension for +Pine Script v6 while keeping the existing grammar pipeline untouched. The release is +3.0.0 and adds: + +- Completion with signatures and documentation +- Hover documentation and signature help +- Document outline (symbols) +- Library integration: local workspace libraries and TradingView published libraries +- Diagnostics from the TradingView compiler (opt-in) +- Commands: new indicator/strategy/library, generate docstring, add type annotations, + open reference +- Two color themes: Pine Dark and Pine Light + +Non-goals for 3.0.0: formatting, rename, go-to-definition across files, semantic +tokens, a language server, running or backtesting scripts, TradingView account login. + +The "Pine Script v6 specific features" from the competing extension's README +(dynamic requests, short-circuit `and`/`or`, typographic text sizes, bold/italic text, +order trimming, negative array indices) are compiler behaviors. The grammar already +covers their surface syntax (`request.*`, `size.*`, `text.format_*`). README gains a +short "v6 compatibility" note; no code is written for them. + +## 2. Architecture + +Single extension, direct VS Code API, no language server. + +``` +src/ + grammar.mjs unchanged + data/ + functions.json unchanged, grammar source + variables.json unchanged, grammar source + constants.json unchanged, grammar source + annotations.json unchanged, grammar source + reference.json NEW, generated, full v6 documentation + extension/ + extension.ts activate(): wires providers, commands, diagnostics + core/ pure TypeScript, never imports "vscode" + reference.ts load + index reference.json + tokenizer.ts line tokenizer (comments, strings, idents, numbers, brackets) + document-model.ts symbols of one document, cached by version + context.ts what is being typed at a position + call-resolver.ts enclosing call, active parameter + markdown.ts hover / completion documentation strings + type-inference.ts static type of an initializer expression + docstring.ts annotation block generator + libraries.ts library export parsing, local + remote models + pine-facade.ts HTTP client for TradingView endpoints + diagnostics.ts compiler response -> neutral diagnostics + templates.ts new-file templates + providers/ thin adapters over core + completion.ts + hover.ts + signature-help.ts + document-symbol.ts + code-action.ts + diagnostics-controller.ts + commands/ + new-file.ts + generate-docstring.ts + add-type-annotations.ts + open-reference.ts + vscode/ + settings.ts typed access to configuration + output.ts "Pine Script" output channel +scripts/ + build-grammar.mjs unchanged + scrape-reference.mjs NEW, produces src/data/reference.json + build-extension.mjs NEW, esbuild bundle +themes/ + pine-dark-color-theme.json NEW + pine-light-color-theme.json NEW +tests/ + unit/, snapshots/ unchanged grammar tests + core/ NEW, vitest tests for src/extension/core + fixtures/*.pine +``` + +Rules: + +- `core/` has no dependency on `vscode`; it works on strings, line arrays and plain + offsets. Everything testable lives there. +- `providers/` and `commands/` translate between VS Code types and core types and + contain no logic beyond that translation. +- `reference.json` is the only documentation source at runtime. It is loaded lazily on + first use and kept in memory. + +## 3. Build and packaging + +- TypeScript 5.x, `tsconfig.json` with `strict`, `module: ESNext`, `moduleResolution: +Bundler`, `target: ES2022`, `noEmit` (esbuild emits). +- esbuild bundles `src/extension/extension.ts` to `dist/extension.cjs`, format `cjs`, + platform `node`, target `node20`, `external: ["vscode"]`, minified for release, with + a source map in development. `reference.json` is bundled by JSON import. +- `package.json`: `"main": "./dist/extension.cjs"`, `"type": "module"` stays. + `activationEvents` is empty; VS Code derives `onLanguage:pinescript` and `onCommand` + from `contributes`. `engines.vscode` becomes `^1.96.0`. +- New dev dependencies: `typescript`, `esbuild`, `@types/vscode`, `@types/node`, + `vitest`. No runtime dependencies. +- Scripts: + - `build:grammar`, `build:grammar:check` (renamed from `build`, `build:check`) + - `build:extension` (esbuild release), `watch` (esbuild watch) + - `scrape:reference` (network, maintainers only) + - `typecheck` (`tsc --noEmit`) + - `test:core` (vitest), `test:unit`, `test:snap` unchanged + - `test` = `build:grammar:check && typecheck && test:core && test:unit && test:snap` + - `vscode:prepublish` = `npm test && npm run build:extension` +- `.vscodeignore` additionally excludes `src/extension/**`, `tests/**`, `docs/**`, + `tsconfig.json`, `vitest.config.*`. `dist/` and `themes/` are included. +- CI adds `typecheck` and `test:core`; the package step verifies the vsix contains + `dist/extension.cjs`. + +## 4. Reference data + +### 4.1 Source and scraper + +`scripts/scrape-reference.mjs` produces `src/data/reference.json` from +https://www.tradingview.com/pine-script-reference/v6/. The page is a client-rendered +application. The scraper first tries to locate the documentation payload inside the +page's JavaScript bundles (plain `fetch`, no browser). If that is not possible, it +falls back to a headless Chromium via an optional `playwright-core` dev dependency +(installed on demand, never part of `npm test` or CI). + +The scraper is idempotent and deterministic: entries sorted by id, stable key order, +so diffs stay reviewable. It is run by maintainers when TradingView updates the +reference; the result is committed. + +### 4.2 Schema + +```jsonc +{ + "version": "6", + "generatedAt": "2026-09-07", + "entries": [ + { + "id": "fun_ta.sma", // anchor on the reference page + "kind": "function", // function | variable | constant | keyword | type | annotation | operator + "name": "ta.sma", + "namespace": "ta", // "" for bare names + "description": "…markdown…", + "overloads": [ + // functions only; one entry per syntax line + { + "syntax": "ta.sma(source, length) → series float", + "params": [ + { "name": "source", "type": "series int/float", "description": "…", "optional": false, "default": null }, + ], + "returns": { "type": "series float", "description": "…" }, + }, + ], + "type": "series float", // variables and constants only + "remarks": "…markdown…", + "example": "//@version=6\n…", + "seeAlso": ["fun_ta.ema", "fun_ta.rma"], + }, + ], +} +``` + +Markdown fields keep inline code and links; links to other entries are rewritten to +`https://www.tradingview.com/pine-script-reference/v6/#`. + +### 4.3 Consistency check + +`build:grammar:check` additionally verifies that every identifier in +`functions.json`, `variables.json` and `constants.json` has an entry in +`reference.json` with the matching kind, and that every function/variable/constant in +`reference.json` appears in the grammar data. A mismatch fails the build with the list +of offending names. This keeps highlighting and documentation in lock-step. + +## 5. Core modules + +### 5.1 tokenizer.ts + +Input: full text. Output: per line, an array of tokens `{ kind, start, end, text }` +with kinds `comment`, `string`, `number`, `ident`, `op`, `open`, `close`, `comma`, +`ws`. Handles `//` comments, `'…'`, `"…"` with escapes, triple-quoted strings spanning +lines (state carried between lines), dotted identifiers as a single `ident` token +(`ta.sma`, `syminfo.mincontract`). Returns also `inString(line, col)` and +`inComment(line, col)` helpers used by every provider to stay silent inside strings and +comments. + +### 5.2 document-model.ts + +`buildModel(text): DocumentModel`, cached by `(uri, version)` in the provider layer. + +```ts +interface DocumentModel { + version: number | null; // from //@version=N, null if missing + scriptKind: 'indicator' | 'strategy' | 'library' | null; + libraryTitle: string | null; // library("Title") + imports: ImportDecl[]; // { owner, name, version, alias, line } + functions: FunctionSymbol[]; // user functions and methods + types: TypeSymbol[]; // type Name + fields + enums: EnumSymbol[]; // enum Name + members + variables: VariableSymbol[]; // top-level and nested declarations +} +interface FunctionSymbol { + name: string; + isMethod: boolean; + isExport: boolean; + params: { name: string; type: string | null; default: string | null }[]; + docs: Annotations; // parsed //@function, //@param, //@returns + range: LineRange; // header line to last indented body line +} +interface VariableSymbol { + name: string; + declaredType: string | null; + qualifier: 'var' | 'varip' | null; + initializer: string | null; + line: number; + column: number; + scope: LineRange; +} +``` + +Parsing is line-based and indentation-aware. A function is a line matching +`[export] [method] name(params) =>`; its body is the following lines with deeper +indentation. Multi-line parameter lists (closing paren on a later line) are joined +before parsing. Annotations are the contiguous `//@…` comment block directly above +the declaration. + +### 5.3 context.ts + +`completionContext(model, tokensOfLine, line, col)` returns one of: + +- `{ kind: 'none' }` inside a string or comment (except annotation context) +- `{ kind: 'annotation', prefix }` after `//@` +- `{ kind: 'import-path', prefix }` on a line starting with `import`, before ` as` +- `{ kind: 'member', receiver, prefix }` after `ident.` where receiver is a built-in + namespace, an import alias, a user type name, or an enum name +- `{ kind: 'named-arg', call, prefix }` inside a call's parentheses where the cursor is + at the start of an argument +- `{ kind: 'identifier', prefix }` otherwise + +### 5.4 call-resolver.ts + +`enclosingCall(tokensUpToCursor)` walks back over balanced brackets and strings and +returns `{ name, argIndex, namedArg: string | null, usedNamedArgs: string[] }` or +`null`. Used by signature help and named-argument completion. + +### 5.5 markdown.ts + +Builds the documentation strings. For a reference entry: fenced `pine` block with the +syntax line(s), description, parameter list (`name` — type — description), returns, +remarks, and a "Reference" link. For a user symbol: syntax reconstructed from the +declaration and the parsed annotations. For a library: title, description, and its +exported symbols. + +### 5.6 type-inference.ts + +`inferType(expr: string, scope: Scope): string | null` where `Scope` gives access to +the reference index, the document model and optional compiler-provided types. + +Resolution order: + +1. Compiler types from the last successful `translate_light` response for this + document version (`variables2`), when remote diagnostics are enabled. +2. Literals: int, float, string, bool, `na` (null), `#RRGGBB` (color). +3. Built-in variables and constants: their `type` from the reference. +4. Built-in function calls: `returns.type` of the first overload whose arity matches; + `input.*` functions map to their value type. +5. `Name.new(...)`: `Name` for user types and built-in drawing types. +6. Identifiers declared earlier in the document with an explicit type. +7. Operators: comparison and logical operators yield `bool`; `+` yields `string` when + either side is a string, else the numeric promotion of both sides; `?:` yields the + type when both branches agree. + +Qualifiers (`series`, `simple`, …) are stripped; only the base type is inserted. Anything +else returns `null` and the variable is skipped. + +### 5.7 docstring.ts + +`generateDocstring(symbol): string[]` returns the comment lines to insert above: + +- function/method: `//@function `, one `//@param

` per parameter, + `//@returns ` (omitted when the body's last expression is a call to a void built-in + such as `plot`, `strategy.entry`, `alert`, `runtime.error`, `log.*`). +- type: `//@type `, one `//@field ` per field. +- enum: `//@enum `, one `//@field ` per member. + +Existing annotations above the declaration are merged: lines already present are kept, +missing ones are added in canonical order, nothing is deleted. + +### 5.8 libraries.ts + +```ts +interface LibraryInfo { + id: string; // "owner/Name/version" for remote, file path for local + title: string; + owner: string | null; + version: string | null; + description: string | null; // //@description or library docs + exports: FunctionSymbol[] | TypeSymbol[] | EnumSymbol[]; + source: 'local' | 'remote'; +} +``` + +`parseLibrary(text, id, source)` uses `document-model.ts` and keeps only `export` +symbols. Local libraries: every workspace file matching +`pinescript.libraries.local.include` whose model has `scriptKind === 'library'`. +Remote libraries: `pine-facade.ts` `libList(prefix)` for completion and +`getScript(scriptIdPart, version)` for exports, parsed with the same function. + +### 5.9 pine-facade.ts + +```ts +interface PineFacade { + libList(prefix: string): Promise; + getScript(scriptIdPart: string, version: string): Promise; // source + translateLight(source: string): Promise; +} +``` + +- Base URL `https://pine-facade.tradingview.com/pine-facade/`. Requests send + `Accept: application/json` and `Referer: https://www.tradingview.com/`. `fetch` is + injected so tests never touch the network. +- `translateLight` POSTs `source` as `application/x-www-form-urlencoded` to + `translate_light?user_name=Guest&pine_id=00000000-0000-0000-0000-000000000000`. +- Caching: `libList` results 10 minutes per prefix; `getScript` results for the session; + `translateLight` keyed by a hash of the source, 100 entries LRU. +- Failure policy: any non-2xx or network error is logged to the output channel. After + three consecutive failures the client backs off for five minutes and every call + resolves to `null`/`[]` immediately. No popups, ever. +- Timeouts: 8 seconds per request via `AbortController`. + +### 5.10 diagnostics.ts + +Maps `result.errors2` and `result.warnings2` to +`{ line, startCol, endCol, message, severity, code }`. Messages are rendered by +substituting `{key}` placeholders from `ctx`. Compiler positions are 1-based; the +mapping converts to 0-based. When the compiler returns `success: false` without +positions, a single diagnostic on line 0 is produced with the returned reason. + +### 5.11 templates.ts + +Three templates following the Pine Script style guide section order (version, header +comment, declaration, imports, constants, inputs, functions, calculations, plots, +alerts). Placeholders use the current date and a title derived from the file name when +one exists. + +## 6. Providers and commands + +### 6.1 Completion (`.`, `/`, `@`, `(`, `,` and identifiers) + +| Context | Items | +| ----------- | ------------------------------------------------------------------------------------- | +| identifier | built-in namespaces and bare built-ins, keywords, types, user symbols, import aliases | +| member | members of the namespace, alias exports, type fields/`new`, enum members | +| named-arg | remaining parameter names of the enclosing call as `name=` | +| annotation | annotation names | +| import-path | local libraries, then remote `libList(prefix)` results as `owner/Name/version` | + +Function items insert `name($1)` as a snippet and trigger signature help. `detail` is the +syntax line, `documentation` the markdown from `markdown.ts`. Deprecated or +version-mismatched suggestions are not a concern: the data is v6 only. + +### 6.2 Hover + +Word under cursor resolved in this order: import alias member, user symbol in scope, +reference entry (longest dotted match first, so `ta.sma` wins over `ta`), import line +(library card), keyword. Returns `null` inside strings and non-annotation comments. + +### 6.3 Signature help (`(`, `,`) + +Uses `call-resolver.ts`. Built-in overloads become separate signatures; the active +signature is the first whose parameter count fits, the active parameter is the named +argument when present, else `argIndex`. User functions and library exports produce one +signature from their declaration and annotations. + +### 6.4 Document symbols + +Functions, methods, types (with fields as children), enums (with members as children), +top-level variables, and the `indicator/strategy/library` declaration as the root. + +### 6.5 Diagnostics controller + +Active only when `pinescript.diagnostics.remote` is `true`. On open, on change +(debounced 600 ms) and on save, the full document text is sent to `translateLight` and +the result mapped into a `DiagnosticCollection` named `pinescript`. Responses that +arrive for an outdated document version are dropped. Successful responses are also +stored per document for `type-inference.ts` and for hover on user functions (compiler +syntax wins over reconstructed syntax when available). Disabling the setting clears all +diagnostics. + +### 6.6 Code actions + +On a line that declares a function, method, type or enum without a complete annotation +block: "Generate docstring" (kind `refactor`). It calls the same core function as the +command. + +### 6.7 Commands + +| Command id | Title | Behavior | +| ------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------- | +| `pinescript.newIndicator` | Pine Script: New Indicator | Opens an untitled `pinescript` document with the template | +| `pinescript.newStrategy` | Pine Script: New Strategy | Same, strategy template | +| `pinescript.newLibrary` | Pine Script: New Library | Same, library template | +| `pinescript.generateDocstring` | Pine Script: Generate Docstring | For the declaration at the cursor (or every declaration in the selection), inserts/merges docs | +| `pinescript.addTypeAnnotations` | Pine Script: Add Type Annotations | For the selection (or whole document), prefixes inferable untyped declarations; shows a summary | +| `pinescript.openReference` | Pine Script: Open Reference | Opens the v6 reference at the anchor of the built-in under the cursor, or the reference root | + +Editor context menu group "Pine Script" shows generateDocstring, addTypeAnnotations and +openReference for `pinescript` documents. The three "New …" commands are in the command +palette only. + +### 6.8 Configuration + +| Setting | Type | Default | Meaning | +| ------------------------------------ | ------- | ----------- | ---------------------------------------------------------------------- | +| `pinescript.completion.enabled` | boolean | `true` | Register the completion provider | +| `pinescript.hover.enabled` | boolean | `true` | Register the hover provider | +| `pinescript.signatureHelp.enabled` | boolean | `true` | Register signature help | +| `pinescript.libraries.local.include` | string | `**/*.pine` | Glob for workspace library discovery | +| `pinescript.libraries.remote` | boolean | `true` | Query TradingView for published libraries in `import` completion/hover | +| `pinescript.diagnostics.remote` | boolean | `false` | Send the document to the TradingView compiler for diagnostics | + +Changing a setting re-registers providers without reload. + +## 7. Themes + +`themes/pine-dark-color-theme.json` (`uiTheme: vs-dark`, label "Pine Dark") and +`themes/pine-light-color-theme.json` (`uiTheme: vs`, label "Pine Light"). Both define: + +- Workbench colors for editor background/foreground, line highlight, selection, + cursor, gutter, sidebar, status bar, tabs and widgets. Dark background `#131722` + (matches the gallery banner), light background `#ffffff`. +- Token colors for every scope the grammar emits: comments, annotations, keywords + (control, declaration, operator words, qualifiers), storage types, generics, + built-in functions per namespace, built-in variables, built-in constants, language + constants, user functions, parameters, variables, strings and escapes, format + placeholders, numbers, hex colors, operators, punctuation. +- Semantic highlighting disabled (no semantic token provider exists). + +## 8. Testing + +- `tests/core/*.test.ts` with vitest: tokenizer (strings, triple quotes, comments, + dotted identifiers), document model (functions with defaults and multi-line params, + methods, types, enums, imports, annotations, scopes), context detection at many + cursor positions, call resolver with nested calls and named args, type inference + table-driven cases, docstring generation and merging, library parsing, pine-facade + client with a fake `fetch` (caching, backoff, timeout), diagnostics mapping from a + recorded compiler response, template rendering. +- Fixtures under `tests/core/fixtures/` are real v6 scripts, including a library and a + script importing it. +- Grammar tests unchanged. Reference consistency check covered by `build:grammar:check`. +- Manual verification in the Extension Development Host before release, following a + checklist added to `CONTRIBUTING.md`. + +## 9. Documentation and release + +- README rewritten around the new capabilities, with a privacy note for the two + remote features and how to disable them, the themes, the commands table, and the + "v6 compatibility" note. +- `CHANGELOG.md` gets `[3.0.0]` with Added/Changed sections; `engines.vscode` bump is a + breaking change and is listed. +- `CONTRIBUTING.md` documents the directory layout, `npm run watch`, F5 debugging, + the scrape script and the manual checklist. +- Marketplace categories add "Themes" and "Linters". Keywords add "completion", + "hover", "themes". +- Version 3.0.0 released through the existing tag-triggered workflow. + +## 10. Privacy, robustness, performance + +- Nothing leaves the machine unless `pinescript.libraries.remote` (import prefix and + library ids only) or `pinescript.diagnostics.remote` (full document text) is on. + The latter is off by default and described in README and in the setting description. +- The endpoints are undocumented. Every remote path degrades to "feature silently + unavailable" and logs to the output channel. The extension must keep working fully + offline. +- `untrustedWorkspaces.supported: true` and `virtualWorkspaces: limited` (local + library discovery needs `file` URIs; everything else works). +- Activation budget: under 100 ms on a warm machine; reference data parsed lazily on + first provider call. Document models are cached per version; completion and hover run + synchronously on cached models. diff --git a/images/example.png b/images/example.png index f05e0e5..acf378e 100644 Binary files a/images/example.png and b/images/example.png differ diff --git a/package-lock.json b/package-lock.json index fa5508a..7320a8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,26 @@ { "name": "pine-script-syntax-highlighter", - "version": "2.0.1", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pine-script-syntax-highlighter", - "version": "2.0.1", + "version": "3.0.0", "license": "MIT", "devDependencies": { + "@types/node": "^22.20.1", + "@types/vscode": "1.96.0", "@vscode/vsce": "^3.9.2", + "esbuild": "^0.28.2", "prettier": "^3.9.6", + "typescript": "^5.9.3", + "vitest": "^5.0.0", "vscode-tmgrammar-test": "^0.1.3" }, "engines": { "node": ">=20", - "vscode": "^1.90.0" + "vscode": "^1.96.0" } }, "node_modules/@azu/format-text": { @@ -249,44 +254,821 @@ "node": ">=6.9.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", + "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", + "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", + "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", + "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", + "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", + "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", + "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", + "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", + "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", + "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", + "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", + "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", + "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", + "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", + "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", + "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@secretlint/config-creator": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", @@ -560,6 +1342,41 @@ "@textlint/ast-node-types": "15.8.0" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -574,6 +1391,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/vscode": { + "version": "1.96.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.96.0.tgz", + "integrity": "sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==", + "dev": true, + "license": "MIT" + }, "node_modules/@typespec/ts-http-runtime": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", @@ -589,6 +1413,44 @@ "node": ">=22.0.0" } }, + "node_modules/@vitest/mocker": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.0.tgz", + "integrity": "sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.0", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.0.tgz", + "integrity": "sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vscode/vsce": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", @@ -860,6 +1722,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1086,6 +1958,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1350,7 +2232,6 @@ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -1544,6 +2425,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", @@ -1573,6 +2461,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1583,6 +2513,16 @@ "node": ">=0.8.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -1594,6 +2534,16 @@ "node": ">=6" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1705,6 +2655,22 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2278,22 +3244,307 @@ "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", "dev": true, - "hasInstallScript": true, - "license": "MIT", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", "optional": true, - "dependencies": { - "node-addon-api": "^4.3.0", - "prebuild-install": "^7.0.1" + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/linkify-it": { @@ -2392,6 +3643,16 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", + "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/markdown-it": { "version": "14.3.1", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.1.tgz", @@ -2570,6 +3831,26 @@ "dev": true, "license": "ISC" }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -2675,6 +3956,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2895,6 +4190,36 @@ "node": ">=4" } }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -3113,6 +4438,41 @@ "node": ">=0.10.0" } }, + "node_modules/rolldown": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", + "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.148.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.7", + "@rolldown/binding-android-arm64": "1.2.7", + "@rolldown/binding-darwin-arm64": "1.2.7", + "@rolldown/binding-darwin-x64": "1.2.7", + "@rolldown/binding-freebsd-x64": "1.2.7", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", + "@rolldown/binding-linux-arm64-gnu": "1.2.7", + "@rolldown/binding-linux-arm64-musl": "1.2.7", + "@rolldown/binding-linux-ppc64-gnu": "1.2.7", + "@rolldown/binding-linux-s390x-gnu": "1.2.7", + "@rolldown/binding-linux-x64-gnu": "1.2.7", + "@rolldown/binding-linux-x64-musl": "1.2.7", + "@rolldown/binding-openharmony-arm64": "1.2.7", + "@rolldown/binding-win32-arm64-msvc": "1.2.7", + "@rolldown/binding-win32-x64-msvc": "1.2.7" + } + }, "node_modules/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", @@ -3299,6 +4659,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -3379,6 +4746,17 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -3415,6 +4793,20 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3643,6 +5035,74 @@ "url": "https://bevry.me/fund" } }, + "node_modules/tinybench": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -3722,6 +5182,20 @@ "underscore": "^1.12.1" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -3746,6 +5220,13 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/unicorn-magic": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", @@ -3808,6 +5289,195 @@ "url": "https://bevry.me/fund" } }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.0.tgz", + "integrity": "sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.0", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.0", + "@vitest/browser-preview": "5.0.0", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.0", + "@vitest/coverage-v8": "5.0.0", + "@vitest/ui": "5.0.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -3997,6 +5667,23 @@ "node": ">=18" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 1669105..fe779e4 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "pine-script-syntax-highlighter", "displayName": "Pine Script Syntax Highlighter", - "description": "Syntax highlighting, snippets and language support for TradingView Pine Script v6.", - "version": "2.0.1", + "description": "Pine Script v6 for Visual Studio Code: highlighting, completion, hover documentation, signature help, snippets, themes and compiler diagnostics.", + "version": "3.0.0", "publisher": "ex-codes", "author": { "name": "Yankı Küçük", @@ -24,11 +24,11 @@ }, "qna": "https://github.com/yankikucuk/pine-script-syntax-highlighter/issues", "engines": { - "vscode": "^1.90.0", + "vscode": "^1.96.0", "node": ">=20" }, "type": "module", - "categories": ["Programming Languages", "Snippets"], + "categories": ["Programming Languages", "Snippets", "Themes", "Linters"], "keywords": [ "pine", "pinescript", @@ -38,7 +38,10 @@ "indicator", "strategy", "syntax", - "highlighting" + "highlighting", + "completion", + "hover", + "themes" ], "contributes": { "languages": [ @@ -73,12 +76,112 @@ "editor.insertSpaces": true, "editor.detectIndentation": false } - } + }, + "configuration": { + "title": "Pine Script", + "properties": { + "pinescript.completion.enabled": { + "type": "boolean", + "default": true, + "description": "Suggest built-ins, keywords, user symbols and named arguments while typing." + }, + "pinescript.hover.enabled": { + "type": "boolean", + "default": true, + "description": "Show documentation when hovering built-ins and user symbols." + }, + "pinescript.signatureHelp.enabled": { + "type": "boolean", + "default": true, + "description": "Show parameter hints inside function calls." + }, + "pinescript.libraries.local.include": { + "type": "string", + "default": "**/*.pine", + "description": "Glob for workspace files scanned for library() declarations." + }, + "pinescript.libraries.remote": { + "type": "boolean", + "default": true, + "description": "Query TradingView for published libraries when completing import statements and hovering imports. Only the typed prefix and library ids are sent." + }, + "pinescript.diagnostics.remote": { + "type": "boolean", + "default": false, + "description": "Send the document text to the TradingView compiler to show errors and warnings. Off by default because the full script leaves your machine." + } + } + }, + "commands": [ + { + "command": "pinescript.generateDocstring", + "title": "Pine Script: Generate Docstring", + "enablement": "editorLangId == pinescript" + }, + { + "command": "pinescript.addTypeAnnotations", + "title": "Pine Script: Add Type Annotations", + "enablement": "editorLangId == pinescript" + }, + { + "command": "pinescript.newIndicator", + "title": "Pine Script: New Indicator" + }, + { + "command": "pinescript.newStrategy", + "title": "Pine Script: New Strategy" + }, + { + "command": "pinescript.newLibrary", + "title": "Pine Script: New Library" + }, + { + "command": "pinescript.openReference", + "title": "Pine Script: Open Reference" + } + ], + "menus": { + "editor/context": [ + { + "command": "pinescript.generateDocstring", + "when": "editorLangId == pinescript", + "group": "1_pinescript@1" + }, + { + "command": "pinescript.addTypeAnnotations", + "when": "editorLangId == pinescript", + "group": "1_pinescript@2" + }, + { + "command": "pinescript.openReference", + "when": "editorLangId == pinescript", + "group": "1_pinescript@3" + } + ] + }, + "themes": [ + { + "label": "Pine Dark", + "uiTheme": "vs-dark", + "path": "./themes/pine-dark-color-theme.json" + }, + { + "label": "Pine Light", + "uiTheme": "vs", + "path": "./themes/pine-light-color-theme.json" + } + ] }, "scripts": { - "build": "node scripts/build-grammar.mjs", - "build:check": "node scripts/build-grammar.mjs --check", - "test": "npm run build:check && npm run test:unit && npm run test:snap", + "build": "npm run build:grammar && npm run build:extension", + "build:grammar": "node scripts/build-grammar.mjs", + "build:grammar:check": "node scripts/build-grammar.mjs --check", + "build:extension": "node scripts/build-extension.mjs", + "watch": "node scripts/build-extension.mjs --watch", + "scrape:reference": "node scripts/scrape-reference.mjs", + "typecheck": "tsc --noEmit", + "test": "npm run build:grammar:check && npm run check:themes && npm run typecheck && npm run test:core && npm run test:unit && npm run test:snap", + "test:core": "vitest run", "test:unit": "vscode-tmgrammar-test -g syntaxes/pinescript.tmLanguage.json \"tests/unit/**/*.pine\"", "test:snap": "vscode-tmgrammar-snap -s source.pine -g syntaxes/pinescript.tmLanguage.json \"tests/snapshots/**/*.pine\"", "test:snap:update": "npm run test:snap -- --updateSnapshot", @@ -86,11 +189,28 @@ "format:check": "prettier --check .", "package": "vsce package", "publish": "vsce publish", - "vscode:prepublish": "npm test" + "vscode:prepublish": "npm test && npm run build:extension", + "check:themes": "node scripts/check-themes.mjs" }, "devDependencies": { + "@types/node": "^22.20.1", + "@types/vscode": "1.96.0", "@vscode/vsce": "^3.9.2", + "esbuild": "^0.28.2", "prettier": "^3.9.6", + "typescript": "^5.9.3", + "vitest": "^5.0.0", "vscode-tmgrammar-test": "^0.1.3" + }, + "main": "./dist/extension.cjs", + "activationEvents": ["onLanguage:pinescript"], + "capabilities": { + "untrustedWorkspaces": { + "supported": true + }, + "virtualWorkspaces": { + "supported": "limited", + "description": "Workspace library discovery needs files on disk." + } } } diff --git a/scripts/build-extension.mjs b/scripts/build-extension.mjs new file mode 100644 index 0000000..7f3b419 --- /dev/null +++ b/scripts/build-extension.mjs @@ -0,0 +1,24 @@ +import { build, context } from 'esbuild'; + +const watch = process.argv.includes('--watch'); + +/** @type {import('esbuild').BuildOptions} */ +const options = { + entryPoints: ['src/extension/extension.ts'], + outfile: 'dist/extension.cjs', + bundle: true, + format: 'cjs', + platform: 'node', + target: 'node20', + external: ['vscode'], + sourcemap: watch ? 'inline' : false, + minify: !watch, + logLevel: 'info', +}; + +if (watch) { + const ctx = await context(options); + await ctx.watch(); +} else { + await build(options); +} diff --git a/scripts/build-grammar.mjs b/scripts/build-grammar.mjs index 84a019e..33dee6e 100644 --- a/scripts/build-grammar.mjs +++ b/scripts/build-grammar.mjs @@ -15,6 +15,51 @@ const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const target = join(root, 'syntaxes', 'pinescript.tmLanguage.json'); const output = JSON.stringify(grammar, null, 2) + '\n'; +const data = (name) => JSON.parse(readFileSync(join(root, 'src', 'data', `${name}.json`), 'utf8')); + +function flatten(groups) { + const names = new Set(); + for (const [ns, members] of Object.entries(groups)) { + for (const m of members) names.add(ns ? `${ns}.${m}` : m); + } + return names; +} + +// The grammar data and the scraped reference must describe the same built-ins. +function checkReferenceConsistency() { + const reference = data('reference').entries; + const types = new Set(reference.filter((e) => e.kind === 'type').map((e) => e.name)); + const languageConstants = new Set(['true', 'false', 'na']); + const byKind = { function: new Set(), variable: new Set(), constant: new Set() }; + for (const e of reference) { + // Type-cast functions (`int()`, `float()`, ...) are highlighted as types; true/false/na as language constants. + if (e.kind === 'function' && types.has(e.name)) continue; + if (e.kind === 'constant' && languageConstants.has(e.name)) continue; + byKind[e.kind]?.add(e.name); + } + const inGrammar = { + function: flatten(data('functions')), + variable: flatten(data('variables')), + constant: flatten(data('constants')), + }; + const problems = []; + for (const kind of Object.keys(byKind)) { + for (const n of inGrammar[kind]) { + if (kind === 'function' && types.has(n)) continue; + if (!byKind[kind].has(n)) problems.push(`${kind} "${n}" is in grammar data but not in reference.json`); + } + for (const n of byKind[kind]) + if (!inGrammar[kind].has(n)) problems.push(`${kind} "${n}" is in reference.json but not in grammar data`); + } + if (problems.length) { + console.error(problems.join('\n')); + console.error(`${problems.length} reference/grammar mismatches. Update src/data/*.json.`); + process.exit(1); + } +} + +checkReferenceConsistency(); + if (process.argv.includes('--check')) { let current = ''; try { diff --git a/scripts/check-themes.mjs b/scripts/check-themes.mjs new file mode 100644 index 0000000..b9cd426 --- /dev/null +++ b/scripts/check-themes.mjs @@ -0,0 +1,26 @@ +// Fails when a scope emitted by the grammar has no rule in a theme. +import { readFileSync } from 'node:fs'; + +const grammar = JSON.parse(readFileSync('syntaxes/pinescript.tmLanguage.json', 'utf8')); +const scopes = new Set(); +JSON.stringify(grammar, (key, value) => { + if ((key === 'name' || key === 'contentName') && typeof value === 'string') + value.split(' ').forEach((s) => scopes.add(s)); + return value; +}); +scopes.delete('Pine'); +scopes.delete('Script'); + +let failed = false; +for (const file of ['themes/pine-dark-color-theme.json', 'themes/pine-light-color-theme.json']) { + const theme = JSON.parse(readFileSync(file, 'utf8')); + const rules = theme.tokenColors.flatMap((r) => (Array.isArray(r.scope) ? r.scope : [r.scope])); + const covered = (scope) => rules.some((r) => scope === r || scope.startsWith(r + '.')); + const missing = [...scopes].filter((s) => !covered(s)); + if (missing.length) { + failed = true; + console.error(`${file} has no rule for:\n ${missing.join('\n ')}`); + } +} +if (failed) process.exit(1); +console.log('Themes cover every grammar scope.'); diff --git a/scripts/lib/extract-reference.js b/scripts/lib/extract-reference.js new file mode 100644 index 0000000..a305f3e --- /dev/null +++ b/scripts/lib/extract-reference.js @@ -0,0 +1,174 @@ +// Runs inside the reference page. Returns { entries: [...] }. +// Kept in its own file so it can be pasted into a browser console for debugging. +async function extractReference() { + const KIND = { + fun: 'function', + var: 'variable', + const: 'constant', + kw: 'keyword', + type: 'type', + an: 'annotation', + op: 'operator', + }; + const BASE = 'https://www.tradingview.com/pine-script-reference/v6/#'; + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + + function clean(text) { + return (text || '') + .replace(/ /g, ' ') + .replace(/[ \t]+\n/g, '\n') + .trim(); + } + + // Converts an element's inline HTML to markdown: → backticks, → link,
→ newline. + function md(el) { + if (!el) return ''; + let out = ''; + for (const node of el.childNodes) { + if (node.nodeType === Node.TEXT_NODE) out += node.textContent; + else if (node.nodeName === 'CODE') out += '`' + node.textContent + '`'; + else if (node.nodeName === 'A') { + const href = node.getAttribute('data-href') || (node.getAttribute('href') || '').replace(/^#/, ''); + out += href ? `[${node.textContent}](${BASE}${href})` : node.textContent; + } else if (node.nodeName === 'BR') out += '\n'; + else out += md(node); + } + return clean(out); + } + + function codeText(pre) { + if (!pre) return ''; + const code = pre.querySelector('code') || pre; + let out = ''; + for (const node of code.childNodes) { + if (node.nodeName === 'BR') out += '\n'; + else out += node.textContent; + } + return clean(out); + } + + // Groups the flat children of an item into sections keyed by sub-header text. + function sections(content) { + const result = { intro: [] }; + let current = 'intro'; + for (const child of content.children) { + if (child.classList.contains('tv-pine-reference-item__header-wrapper')) continue; + if (child.classList.contains('tv-pine-reference-item__sub-header')) { + current = clean(child.textContent).replace(/\s+/g, ' '); + if (!result[current]) result[current] = []; + continue; + } + result[current].push(child); + } + return result; + } + + function parseArgs(nodes) { + const args = []; + for (const node of nodes) { + const typeSpan = node.querySelector('.tv-pine-reference-item__arg-type'); + if (!typeSpan) continue; + const head = clean(typeSpan.textContent); + const m = head.match(/^([\w.]+)\s*\(([^)]*)\)/); + if (!m) continue; + const clone = node.cloneNode(true); + clone.querySelector('.tv-pine-reference-item__arg-type').remove(); + const description = md(clone); + const defMatch = + description.match(/[Dd]efault(?: value)? is ([^.\n]+)/) || description.match(/[Dd]efault:\s*([^.\n]+)/); + args.push({ + name: m[1], + type: m[2].trim(), + description, + optional: /\boptional\b/i.test(description), + default: defMatch ? defMatch[1].trim() : null, + }); + } + return args; + } + + function readOverloadBlock(content) { + const s = sections(content); + const syntaxKey = Object.keys(s).find((k) => k.startsWith('Syntax')); + const syntax = syntaxKey + ? s[syntaxKey].map((n) => codeText(n.matches('pre') ? n : n.querySelector('pre'))).filter(Boolean) + : []; + const returnsNodes = s['Returns'] || []; + const returns = returnsNodes.length ? returnsNodes.map(md).filter(Boolean).join('\n\n') : ''; + return { s, syntax, params: parseArgs(s['Arguments'] || []), returns }; + } + + function returnType(syntax) { + const m = syntax.match(/→\s*(.+)$/); + return m ? m[1].trim() : ''; + } + + const items = [...document.querySelectorAll('.tv-pine-reference-item[id]')]; + const entries = []; + for (const item of items) { + const id = item.id; + const prefix = id.split('_')[0]; + const kind = KIND[prefix]; + if (!kind) continue; + const content = item.querySelector('.tv-pine-reference-item__content'); + const header = clean(item.querySelector('.tv-pine-reference-item__header')?.textContent).replace(/\(\)$/, ''); + // `array.new()` is documented with its generic parameter; the identifier is `array.new`. + const name = header.replace(/<[^>]*>/g, ''); + const namespace = name.includes('.') ? name.slice(0, name.lastIndexOf('.')) : ''; + + const first = readOverloadBlock(content); + const s = first.s; + const description = (s.intro || []).map(md).filter(Boolean).join('\n\n'); + const remarks = (s['Remarks'] || []).map(md).filter(Boolean).join('\n\n'); + const example = (s['Example'] || []) + .map((n) => codeText(n.matches('pre') ? n : n.querySelector('pre'))) + .filter(Boolean) + .join('\n\n'); + const seeAlso = [...(s['See also'] || []).flatMap((n) => [...n.querySelectorAll('a[data-href]')])].map((a) => + a.getAttribute('data-href'), + ); + const fields = parseArgs(s['Fields'] || []); + const typeNodes = s['Type'] || []; + const type = typeNodes.length ? clean(typeNodes[0].textContent) : null; + + const overloads = []; + if (kind === 'function') { + const anchors = [...content.querySelectorAll('a[data-href^="' + id + '-"]')]; + if (anchors.length <= 1) { + for (const syntax of first.syntax) { + overloads.push({ + syntax, + params: first.params, + returns: { type: returnType(syntax), description: first.returns }, + }); + } + } else { + for (let n = 0; n < anchors.length; n++) { + // Selecting an overload re-renders the item, so re-query it by id after every click. + const fresh = () => document.getElementById(id).querySelector('.tv-pine-reference-item__content'); + const anchor = fresh().querySelectorAll('a[data-href^="' + id + '-"]')[n]; + const wanted = codeText(anchor.querySelector('pre')); + anchor.click(); + let selected = null; + for (let tries = 0; tries < 40; tries++) { + selected = fresh().querySelector('pre.tv-pine-reference-item__syntax.selected'); + if (selected && codeText(selected) === wanted) break; + selected = null; + await sleep(25); + } + if (!selected) throw new Error('Overload ' + n + ' of ' + id + ' did not become selected'); + const block = readOverloadBlock(fresh()); + overloads.push({ + syntax: wanted, + params: block.params, + returns: { type: returnType(wanted), description: block.returns }, + }); + } + } + } + + entries.push({ id, kind, name, namespace, description, overloads, fields, type, remarks, example, seeAlso }); + } + entries.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); + return { entries }; +} diff --git a/scripts/scrape-reference.mjs b/scripts/scrape-reference.mjs new file mode 100644 index 0000000..f760281 --- /dev/null +++ b/scripts/scrape-reference.mjs @@ -0,0 +1,42 @@ +// Produces src/data/reference.json from the Pine Script v6 reference. +// Needs playwright-core and a Chromium: `npx playwright-core install chromium` once. +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(here, '..'); +const URL = 'https://www.tradingview.com/pine-script-reference/v6/'; +const OUT = path.join(root, 'src/data/reference.json'); + +let chromium; +try { + ({ chromium } = await import('playwright-core')); +} catch { + console.error( + 'playwright-core is not installed. Run: npm install --no-save playwright-core && npx playwright-core install chromium', + ); + process.exit(1); +} + +const extractor = await readFile(path.join(here, 'lib/extract-reference.js'), 'utf8'); +const browser = await chromium.launch(); +try { + const page = await browser.newPage({ viewport: { width: 1280, height: 720 } }); + await page.goto(URL, { waitUntil: 'networkidle' }); + await page.waitForSelector('.tv-pine-reference-item[id]'); + const { entries } = await page.evaluate(`${extractor}; extractReference();`); + if (entries.length < 900) { + throw new Error(`Only ${entries.length} entries extracted; the page structure may have changed.`); + } + // Overload arguments are read after selecting each overload; this known case catches a silent regression. + const tostring = entries.find((e) => e.id === 'fun_str.tostring'); + if (!tostring || !tostring.overloads.some((o) => o.params.length === 2)) { + throw new Error('Overload extraction failed: str.tostring should have a two-parameter overload.'); + } + const data = { version: '6', generatedAt: new Date().toISOString().slice(0, 10), entries }; + await writeFile(OUT, JSON.stringify(data, null, 1) + '\n'); + console.log(`Wrote ${entries.length} entries to ${path.relative(root, OUT)}`); +} finally { + await browser.close(); +} diff --git a/src/data/functions.json b/src/data/functions.json index b603f98..d593fec 100644 --- a/src/data/functions.json +++ b/src/data/functions.json @@ -194,7 +194,6 @@ "set_x1", "set_x2", "set_xloc", - "set_xy", "set_xy1", "set_xy2", "set_y1", diff --git a/src/data/reference.json b/src/data/reference.json new file mode 100644 index 0000000..52203ed --- /dev/null +++ b/src/data/reference.json @@ -0,0 +1,35687 @@ +{ + "version": "6", + "generatedAt": "2026-09-07", + "entries": [ + { + "id": "an_@description", + "kind": "annotation", + "name": "@description", + "namespace": "", + "description": "Sets a custom description for scripts that use the [library()](https://www.tradingview.com/pine-script-reference/v6/#fun_library) declaration statement. The text provided with this annotation will be used to pre-fill the \"Description\" field in the publication dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\n// @description Provides a tool to quickly output a label on the chart.\nlibrary(\"MyLibrary\")\n\n// @function Outputs a label with `labelText` on the bar's high.\n// @param labelText (series string) The text to display on the label.\n// @returns Drawn label.\nexport drawLabel(string labelText) =>\n label.new(bar_index, high, text = labelText)", + "seeAlso": [] + }, + { + "id": "an_@enum", + "kind": "annotation", + "name": "@enum", + "namespace": "", + "description": "If placed above an enum declaration, it adds a custom description for the enum. The Pine Editor's autosuggest uses this description and displays it when a user hovers over the enum name. When used in library scripts, the descriptions of all enums using the [export](https://www.tradingview.com/pine-script-reference/v6/#kw_export) keyword will pre-fill the \"Description\" field in the publication dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"Session highlight\", overlay = true)\n\n//@enum Contains fields with popular timezones as titles.\n//@field exch Has an empty string as the title to represent the chart timezone.\nenum tz\n utc = \"UTC\"\n exch = \"\"\n ny = \"America/New_York\"\n chi = \"America/Chicago\"\n lon = \"Europe/London\"\n tok = \"Asia/Tokyo\"\n\n//@variable The session string.\nselectedSession = input.session(\"1200-1500\", \"Session\")\n//@variable The selected timezone. The input's dropdown contains the fields in the `tz` enum.\nselectedTimezone = input.enum(tz.utc, \"Session Timezone\")\n\n//@variable Is `true` if the current bar's time is in the specified session.\nbool inSession = false\nif not na(time(\"\", selectedSession, str.tostring(selectedTimezone)))\n inSession := true\n\n// Highlight the background when `inSession` is `true`.\nbgcolor(inSession ? color.new(color.green, 90) : na, title = \"Active session highlight\")", + "seeAlso": [] + }, + { + "id": "an_@field", + "kind": "annotation", + "name": "@field", + "namespace": "", + "description": "If placed above a [type](https://www.tradingview.com/pine-script-reference/v6/#kw_type) or [enum](https://www.tradingview.com/pine-script-reference/v6/#kw_enum) declaration, it adds a custom description for a field of the type/enum. After the annotation, users should specify the field name, followed by its description.\n\nThe Pine Editor's autosuggest uses this description and displays it when a user hovers over the type/enum or field name. When used in [library()](https://www.tradingview.com/pine-script-reference/v6/#fun_library) scripts, the descriptions of all types/enums using the [export](https://www.tradingview.com/pine-script-reference/v6/#kw_export) keyword will pre-fill the \"Description\" field in the publication dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"New high over the last 20 bars\", overlay = true)\n\n//@type A point on a chart.\n//@field index The index of the bar where the point is located, i.e., its `x` coordinate.\n//@field price The price where the point is located, i.e., its `y` coordinate.\ntype Point\n int index\n float price\n\n//@variable If the current `high` is the highest over the last 20 bars, returns a new `Point` instance, `na` otherwise.\nPoint highest = na\n\nif ta.highestbars(high, 20) == 0\n highest := Point.new(bar_index, high)\n label.new(highest.index, highest.price, str.tostring(highest.price))", + "seeAlso": [] + }, + { + "id": "an_@function", + "kind": "annotation", + "name": "@function", + "namespace": "", + "description": "If placed above a function declaration, it adds a custom description for the function.\n\nThe Pine Editor's autosuggest uses this description and displays it when a user hovers over the function name. When used in [library()](https://www.tradingview.com/pine-script-reference/v6/#fun_library) scripts, the descriptions of all functions using the [export](https://www.tradingview.com/pine-script-reference/v6/#kw_export) keyword will pre-fill the \"Description\" field in the publication dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\n// @description Provides a tool to quickly output a label on the chart.\nlibrary(\"MyLibrary\")\n\n// @function Outputs a label with `labelText` on the bar's high.\n// @param labelText (series string) The text to display on the label.\n// @returns Drawn label.\nexport drawLabel(string labelText) =>\n label.new(bar_index, high, text = labelText)", + "seeAlso": [] + }, + { + "id": "an_@param", + "kind": "annotation", + "name": "@param", + "namespace": "", + "description": "If placed above a function declaration, it adds a custom description for a function parameter. After the annotation, users should specify the parameter name, then its description.\n\nThe Pine Editor's autosuggest uses this description and displays it when a user hovers over the function name. When used in [library()](https://www.tradingview.com/pine-script-reference/v6/#fun_library) scripts, the descriptions of all functions using the [export](https://www.tradingview.com/pine-script-reference/v6/#kw_export) keyword will pre-fill the \"Description\" field in the publication dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\n// @description Provides a tool to quickly output a label on the chart.\nlibrary(\"MyLibrary\")\n\n// @function Outputs a label with `labelText` on the bar's high.\n// @param labelText (series string) The text to display on the label.\n// @returns Drawn label.\nexport drawLabel(string labelText) =>\n label.new(bar_index, high, text = labelText)", + "seeAlso": [] + }, + { + "id": "an_@returns", + "kind": "annotation", + "name": "@returns", + "namespace": "", + "description": "If placed above a function declaration, it adds a custom description for what that function returns.\n\nThe Pine Editor's autosuggest uses this description and displays it when a user hovers over the function name. When used in [library()](https://www.tradingview.com/pine-script-reference/v6/#fun_library) scripts, the descriptions of all functions using the [export](https://www.tradingview.com/pine-script-reference/v6/#kw_export) keyword will pre-fill the \"Description\" field in the publication dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\n// @description Provides a tool to quickly output a label on the chart.\nlibrary(\"MyLibrary\")\n\n// @function Outputs a label with `labelText` on the bar's high.\n// @param labelText (series string) The text to display on the label.\n// @returns Drawn label.\nexport drawLabel(string labelText) =>\n label.new(bar_index, high, text = labelText)", + "seeAlso": [] + }, + { + "id": "an_@strategy_alert_message", + "kind": "annotation", + "name": "@strategy_alert_message", + "namespace": "", + "description": "If used within a [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy) script, it provides a default message to pre-fill the \"Message\" field in the alert creation dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nstrategy(\"My strategy\", overlay=true, margin_long=100, margin_short=100)\n//@strategy_alert_message Strategy alert on symbol {{ticker}}\n\nlongCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))\nif (longCondition)\n strategy.entry(\"My Long Entry Id\", strategy.long)\nstrategy.exit(\"Exit\", \"My Long Entry Id\", profit = 10 / syminfo.mintick, loss = 10 / syminfo.mintick)", + "seeAlso": [] + }, + { + "id": "an_@type", + "kind": "annotation", + "name": "@type", + "namespace": "", + "description": "If placed above a type declaration, it adds a custom description for the type.\n\nThe Pine Editor's autosuggest uses this description and displays it when a user hovers over the type name. When used in [library()](https://www.tradingview.com/pine-script-reference/v6/#fun_library) scripts, the descriptions of all types using the [export](https://www.tradingview.com/pine-script-reference/v6/#kw_export) keyword will pre-fill the \"Description\" field in the publication dialogue.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"New high over the last 20 bars\", overlay = true)\n\n//@type A point on a chart.\n//@field index The index of the bar where the point is located, i.e., its `x` coordinate.\n//@field price The price where the point is located, i.e., its `y` coordinate.\ntype Point\n int index\n float price\n\n//@variable If the current `high` is the highest over the last 20 bars, returns a new `Point` instance, `na` otherwise.\nPoint highest = na\n\nif ta.highestbars(high, 20) == 0\n highest := Point.new(bar_index, high)\n label.new(highest.index, highest.price, str.tostring(highest.price))", + "seeAlso": [] + }, + { + "id": "an_@variable", + "kind": "annotation", + "name": "@variable", + "namespace": "", + "description": "If placed above a variable declaration, it adds a custom description for the variable.\n\nThe Pine Editor's autosuggest uses this description and displays it when a user hovers over the variable name.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"New high over the last 20 bars\", overlay = true)\n\n//@type A point on a chart.\n//@field index The index of the bar where the point is located, i.e., its `x` coordinate.\n//@field price The price where the point is located, i.e., its `y` coordinate.\ntype Point\n int index\n float price\n\n//@variable If the current `high` is the highest over the last 20 bars, returns a new `Point` instance, `na` otherwise.\nPoint highest = na\n\nif ta.highestbars(high, 20) == 0\n highest := Point.new(bar_index, high)\n label.new(highest.index, highest.price, str.tostring(highest.price))", + "seeAlso": [] + }, + { + "id": "an_@version=", + "kind": "annotation", + "name": "@version=", + "namespace": "", + "description": "Specifies the Pine Script® version that the script will use. The number in this annotation should not be confused with the script's version number, which updates on every saved change to the code.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "The version should always be specified. Otherwise, for compatibility reasons, the script will be compiled using Pine Script® v1, which lacks most of the newer features and is bound to confuse. This annotation can be anywhere within a script, but we recommend placing it at the top of the code for readability.", + "example": "//@version=6\nindicator(\"Pine v6 Indicator\")\nplot(close)\n\n//This indicator has no version annotation, so it will try to use v1.\n//Pine Script® v1 has no function named `indicator()`, so the script will not compile.\nindicator(\"Pine v1 Indicator\")\nplot(close)", + "seeAlso": [] + }, + { + "id": "const_adjustment.dividends", + "kind": "constant", + "name": "adjustment.dividends", + "namespace": "adjustment", + "description": "Constant for dividends adjustment type (dividends adjustment is applied).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "const_adjustment.none", + "const_adjustment.splits", + "fun_ticker.new" + ] + }, + { + "id": "const_adjustment.none", + "kind": "constant", + "name": "adjustment.none", + "namespace": "adjustment", + "description": "Constant for none adjustment type (no adjustment is applied).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "const_adjustment.splits", + "const_adjustment.dividends", + "fun_ticker.new" + ] + }, + { + "id": "const_adjustment.splits", + "kind": "constant", + "name": "adjustment.splits", + "namespace": "adjustment", + "description": "Constant for splits adjustment type (splits adjustment is applied).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "const_adjustment.none", + "const_adjustment.dividends", + "fun_ticker.new" + ] + }, + { + "id": "const_alert.freq_all", + "kind": "constant", + "name": "alert.freq_all", + "namespace": "alert", + "description": "A named constant for use with the `freq` parameter of the alert() function.\n\nAll function calls trigger the alert.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_alert" + ] + }, + { + "id": "const_alert.freq_once_per_bar", + "kind": "constant", + "name": "alert.freq_once_per_bar", + "namespace": "alert", + "description": "A named constant for use with the `freq` parameter of the alert() function.\n\nThe first function call during the bar triggers the alert.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_alert" + ] + }, + { + "id": "const_alert.freq_once_per_bar_close", + "kind": "constant", + "name": "alert.freq_once_per_bar_close", + "namespace": "alert", + "description": "A named constant for use with the `freq` parameter of the alert() function.\n\nThe function call triggers the alert only when it occurs during the last script iteration of the real-time bar, when it closes.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_alert" + ] + }, + { + "id": "const_backadjustment.inherit", + "kind": "constant", + "name": "backadjustment.inherit", + "namespace": "backadjustment", + "description": "A constant to specify the value of the `backadjustment` parameter in [ticker.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.new) and [ticker.modify()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.modify) functions.", + "overloads": [], + "fields": [], + "type": "const backadjustment", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_ticker.new", + "fun_ticker.modify", + "const_backadjustment.on", + "const_backadjustment.off" + ] + }, + { + "id": "const_backadjustment.off", + "kind": "constant", + "name": "backadjustment.off", + "namespace": "backadjustment", + "description": "A constant to specify the value of the `backadjustment` parameter in [ticker.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.new) and [ticker.modify()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.modify) functions.", + "overloads": [], + "fields": [], + "type": "const backadjustment", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_ticker.new", + "fun_ticker.modify", + "const_backadjustment.on", + "const_backadjustment.inherit" + ] + }, + { + "id": "const_backadjustment.on", + "kind": "constant", + "name": "backadjustment.on", + "namespace": "backadjustment", + "description": "A constant to specify the value of the `backadjustment` parameter in [ticker.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.new) and [ticker.modify()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.modify) functions.", + "overloads": [], + "fields": [], + "type": "const backadjustment", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_ticker.new", + "fun_ticker.modify", + "const_backadjustment.inherit", + "const_backadjustment.off" + ] + }, + { + "id": "const_barmerge.gaps_off", + "kind": "constant", + "name": "barmerge.gaps_off", + "namespace": "barmerge", + "description": "Merge strategy for requested data. Data is merged continuously without gaps, all the gaps are filled with the previous nearest existing value.", + "overloads": [], + "fields": [], + "type": "const barmerge_gaps", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.security", + "const_barmerge.gaps_on" + ] + }, + { + "id": "const_barmerge.gaps_on", + "kind": "constant", + "name": "barmerge.gaps_on", + "namespace": "barmerge", + "description": "Merge strategy for requested data. Data is merged with possible gaps ([na](https://www.tradingview.com/pine-script-reference/v6/#var_na) values).", + "overloads": [], + "fields": [], + "type": "const barmerge_gaps", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.security", + "const_barmerge.gaps_off" + ] + }, + { + "id": "const_barmerge.lookahead_off", + "kind": "constant", + "name": "barmerge.lookahead_off", + "namespace": "barmerge", + "description": "Merge strategy for the requested data position. Requested barset is merged with current barset in the order of sorting bars by their close time. This merge strategy disables effect of getting data from \"future\" on calculation on history.", + "overloads": [], + "fields": [], + "type": "const barmerge_lookahead", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.security", + "const_barmerge.lookahead_on" + ] + }, + { + "id": "const_barmerge.lookahead_on", + "kind": "constant", + "name": "barmerge.lookahead_on", + "namespace": "barmerge", + "description": "Merge strategy for the requested data position. Requested barset is merged with current barset in the order of sorting bars by their opening time. This merge strategy can lead to undesirable effect of getting data from \"future\" on calculation on history. This is unacceptable in backtesting strategies, but can be useful in indicators.", + "overloads": [], + "fields": [], + "type": "const barmerge_lookahead", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.security", + "const_barmerge.lookahead_off" + ] + }, + { + "id": "const_color.aqua", + "kind": "constant", + "name": "color.aqua", + "namespace": "color", + "description": "Is a named constant for #00BCD4 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.orange" + ] + }, + { + "id": "const_color.black", + "kind": "constant", + "name": "color.black", + "namespace": "color", + "description": "Is a named constant for #363A45 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.blue", + "kind": "constant", + "name": "color.blue", + "namespace": "color", + "description": "Is a named constant for #2962ff color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.fuchsia", + "kind": "constant", + "name": "color.fuchsia", + "namespace": "color", + "description": "Is a named constant for #E040FB color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.gray", + "kind": "constant", + "name": "color.gray", + "namespace": "color", + "description": "Is a named constant for #787B86 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.green", + "kind": "constant", + "name": "color.green", + "namespace": "color", + "description": "Is a named constant for #4CAF50 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.lime", + "kind": "constant", + "name": "color.lime", + "namespace": "color", + "description": "Is a named constant for #00E676 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.maroon", + "kind": "constant", + "name": "color.maroon", + "namespace": "color", + "description": "Is a named constant for #880E4F color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.navy", + "kind": "constant", + "name": "color.navy", + "namespace": "color", + "description": "Is a named constant for #311B92 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.olive", + "kind": "constant", + "name": "color.olive", + "namespace": "color", + "description": "Is a named constant for #808000 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.orange", + "kind": "constant", + "name": "color.orange", + "namespace": "color", + "description": "Is a named constant for #FF9800 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua" + ] + }, + { + "id": "const_color.purple", + "kind": "constant", + "name": "color.purple", + "namespace": "color", + "description": "Is a named constant for #9C27B0 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.red", + "kind": "constant", + "name": "color.red", + "namespace": "color", + "description": "Is a named constant for #F23645 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.silver", + "kind": "constant", + "name": "color.silver", + "namespace": "color", + "description": "Is a named constant for #B2B5BE color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.teal", + "kind": "constant", + "name": "color.teal", + "namespace": "color", + "description": "Is a named constant for #089981 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.white", + "kind": "constant", + "name": "color.white", + "namespace": "color", + "description": "Is a named constant for #FFFFFF color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.yellow", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_color.yellow", + "kind": "constant", + "name": "color.yellow", + "namespace": "color", + "description": "Is a named constant for #FDD835 color.", + "overloads": [], + "fields": [], + "type": "const color", + "remarks": "", + "example": "", + "seeAlso": [ + "const_color.black", + "const_color.silver", + "const_color.gray", + "const_color.white", + "const_color.maroon", + "const_color.red", + "const_color.purple", + "const_color.fuchsia", + "const_color.green", + "const_color.lime", + "const_color.olive", + "const_color.navy", + "const_color.blue", + "const_color.teal", + "const_color.aqua", + "const_color.orange" + ] + }, + { + "id": "const_currency.AED", + "kind": "constant", + "name": "currency.AED", + "namespace": "currency", + "description": "Arab Emirates Dirham.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.ARS", + "kind": "constant", + "name": "currency.ARS", + "namespace": "currency", + "description": "Argentine Pesos.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.AUD", + "kind": "constant", + "name": "currency.AUD", + "namespace": "currency", + "description": "Australian dollar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.BDT", + "kind": "constant", + "name": "currency.BDT", + "namespace": "currency", + "description": "Bangladeshi Taka.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.BHD", + "kind": "constant", + "name": "currency.BHD", + "namespace": "currency", + "description": "Bahraini Dinar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.BRL", + "kind": "constant", + "name": "currency.BRL", + "namespace": "currency", + "description": "Brazilian real.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.BTC", + "kind": "constant", + "name": "currency.BTC", + "namespace": "currency", + "description": "Bitcoin.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.CAD", + "kind": "constant", + "name": "currency.CAD", + "namespace": "currency", + "description": "Canadian dollar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.CHF", + "kind": "constant", + "name": "currency.CHF", + "namespace": "currency", + "description": "Swiss franc.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.CLP", + "kind": "constant", + "name": "currency.CLP", + "namespace": "currency", + "description": "Chilean Peso.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.CNY", + "kind": "constant", + "name": "currency.CNY", + "namespace": "currency", + "description": "Chinese Yuan.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.COP", + "kind": "constant", + "name": "currency.COP", + "namespace": "currency", + "description": "Colombian Peso.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.CZK", + "kind": "constant", + "name": "currency.CZK", + "namespace": "currency", + "description": "Czech Koruna.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.DKK", + "kind": "constant", + "name": "currency.DKK", + "namespace": "currency", + "description": "Danish Krone.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.EGP", + "kind": "constant", + "name": "currency.EGP", + "namespace": "currency", + "description": "Egyptian pound.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.ETH", + "kind": "constant", + "name": "currency.ETH", + "namespace": "currency", + "description": "Ethereum.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.EUR", + "kind": "constant", + "name": "currency.EUR", + "namespace": "currency", + "description": "Euro.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.GBP", + "kind": "constant", + "name": "currency.GBP", + "namespace": "currency", + "description": "Pound sterling.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.HKD", + "kind": "constant", + "name": "currency.HKD", + "namespace": "currency", + "description": "Hong Kong dollar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.HUF", + "kind": "constant", + "name": "currency.HUF", + "namespace": "currency", + "description": "Hungarian Forint.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.IDR", + "kind": "constant", + "name": "currency.IDR", + "namespace": "currency", + "description": "Indonesian Rupiah.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.ILS", + "kind": "constant", + "name": "currency.ILS", + "namespace": "currency", + "description": "Israeli New Shekel.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.INR", + "kind": "constant", + "name": "currency.INR", + "namespace": "currency", + "description": "Indian rupee.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.ISK", + "kind": "constant", + "name": "currency.ISK", + "namespace": "currency", + "description": "Icelandic Krona.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.JPY", + "kind": "constant", + "name": "currency.JPY", + "namespace": "currency", + "description": "Japanese yen.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.KES", + "kind": "constant", + "name": "currency.KES", + "namespace": "currency", + "description": "Kenyan Shilling.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.KRW", + "kind": "constant", + "name": "currency.KRW", + "namespace": "currency", + "description": "South Korean won.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.KWD", + "kind": "constant", + "name": "currency.KWD", + "namespace": "currency", + "description": "Kuwaiti Dinar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.LKR", + "kind": "constant", + "name": "currency.LKR", + "namespace": "currency", + "description": "Sri Lankan Rupee.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.MAD", + "kind": "constant", + "name": "currency.MAD", + "namespace": "currency", + "description": "Moroccan Dirham.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.MXN", + "kind": "constant", + "name": "currency.MXN", + "namespace": "currency", + "description": "Mexican Peso.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.MYR", + "kind": "constant", + "name": "currency.MYR", + "namespace": "currency", + "description": "Malaysian ringgit.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.NGN", + "kind": "constant", + "name": "currency.NGN", + "namespace": "currency", + "description": "Nigerian Naira.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.NOK", + "kind": "constant", + "name": "currency.NOK", + "namespace": "currency", + "description": "Norwegian krone.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.NONE", + "kind": "constant", + "name": "currency.NONE", + "namespace": "currency", + "description": "Unspecified currency.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.NZD", + "kind": "constant", + "name": "currency.NZD", + "namespace": "currency", + "description": "New Zealand dollar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.PEN", + "kind": "constant", + "name": "currency.PEN", + "namespace": "currency", + "description": "Peruvian sol.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.PHP", + "kind": "constant", + "name": "currency.PHP", + "namespace": "currency", + "description": "Philippine Peso.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.PKR", + "kind": "constant", + "name": "currency.PKR", + "namespace": "currency", + "description": "Pakistani rupee.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.PLN", + "kind": "constant", + "name": "currency.PLN", + "namespace": "currency", + "description": "Polish zloty.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.QAR", + "kind": "constant", + "name": "currency.QAR", + "namespace": "currency", + "description": "Qatari Riyal.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.RON", + "kind": "constant", + "name": "currency.RON", + "namespace": "currency", + "description": "Romanian Leu.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.RSD", + "kind": "constant", + "name": "currency.RSD", + "namespace": "currency", + "description": "Serbian Dinar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.RUB", + "kind": "constant", + "name": "currency.RUB", + "namespace": "currency", + "description": "Russian ruble.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.SAR", + "kind": "constant", + "name": "currency.SAR", + "namespace": "currency", + "description": "Saudi Riyal.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.SEK", + "kind": "constant", + "name": "currency.SEK", + "namespace": "currency", + "description": "Swedish krona.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.SGD", + "kind": "constant", + "name": "currency.SGD", + "namespace": "currency", + "description": "Singapore dollar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.THB", + "kind": "constant", + "name": "currency.THB", + "namespace": "currency", + "description": "Thai Baht.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.TND", + "kind": "constant", + "name": "currency.TND", + "namespace": "currency", + "description": "Tunisian Dinar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.TRY", + "kind": "constant", + "name": "currency.TRY", + "namespace": "currency", + "description": "Turkish lira.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.TWD", + "kind": "constant", + "name": "currency.TWD", + "namespace": "currency", + "description": "New Taiwan Dollar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.USD", + "kind": "constant", + "name": "currency.USD", + "namespace": "currency", + "description": "United States dollar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.USDT", + "kind": "constant", + "name": "currency.USDT", + "namespace": "currency", + "description": "Tether.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.VES", + "kind": "constant", + "name": "currency.VES", + "namespace": "currency", + "description": "Venezuelan Bolivar.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.VND", + "kind": "constant", + "name": "currency.VND", + "namespace": "currency", + "description": "Vietnamese Dong.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_currency.ZAR", + "kind": "constant", + "name": "currency.ZAR", + "namespace": "currency", + "description": "South African rand.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_dayofweek.friday", + "kind": "constant", + "name": "dayofweek.friday", + "namespace": "dayofweek", + "description": "Is a named constant for return value of [dayofweek()](https://www.tradingview.com/pine-script-reference/v6/#fun_dayofweek) function and value of [dayofweek](https://www.tradingview.com/pine-script-reference/v6/#var_dayofweek) variable.", + "overloads": [], + "fields": [], + "type": "const int", + "remarks": "", + "example": "", + "seeAlso": [ + "const_dayofweek.sunday", + "const_dayofweek.monday", + "const_dayofweek.tuesday", + "const_dayofweek.wednesday", + "const_dayofweek.thursday", + "const_dayofweek.saturday" + ] + }, + { + "id": "const_dayofweek.monday", + "kind": "constant", + "name": "dayofweek.monday", + "namespace": "dayofweek", + "description": "Is a named constant for return value of [dayofweek()](https://www.tradingview.com/pine-script-reference/v6/#fun_dayofweek) function and value of [dayofweek](https://www.tradingview.com/pine-script-reference/v6/#var_dayofweek) variable.", + "overloads": [], + "fields": [], + "type": "const int", + "remarks": "", + "example": "", + "seeAlso": [ + "const_dayofweek.sunday", + "const_dayofweek.tuesday", + "const_dayofweek.wednesday", + "const_dayofweek.thursday", + "const_dayofweek.friday", + "const_dayofweek.saturday" + ] + }, + { + "id": "const_dayofweek.saturday", + "kind": "constant", + "name": "dayofweek.saturday", + "namespace": "dayofweek", + "description": "Is a named constant for return value of [dayofweek()](https://www.tradingview.com/pine-script-reference/v6/#fun_dayofweek) function and value of [dayofweek](https://www.tradingview.com/pine-script-reference/v6/#var_dayofweek) variable.", + "overloads": [], + "fields": [], + "type": "const int", + "remarks": "", + "example": "", + "seeAlso": [ + "const_dayofweek.sunday", + "const_dayofweek.monday", + "const_dayofweek.tuesday", + "const_dayofweek.wednesday", + "const_dayofweek.thursday", + "const_dayofweek.friday" + ] + }, + { + "id": "const_dayofweek.sunday", + "kind": "constant", + "name": "dayofweek.sunday", + "namespace": "dayofweek", + "description": "Is a named constant for return value of [dayofweek()](https://www.tradingview.com/pine-script-reference/v6/#fun_dayofweek) function and value of [dayofweek](https://www.tradingview.com/pine-script-reference/v6/#var_dayofweek) variable.", + "overloads": [], + "fields": [], + "type": "const int", + "remarks": "", + "example": "", + "seeAlso": [ + "const_dayofweek.monday", + "const_dayofweek.tuesday", + "const_dayofweek.wednesday", + "const_dayofweek.thursday", + "const_dayofweek.friday", + "const_dayofweek.saturday" + ] + }, + { + "id": "const_dayofweek.thursday", + "kind": "constant", + "name": "dayofweek.thursday", + "namespace": "dayofweek", + "description": "Is a named constant for return value of [dayofweek()](https://www.tradingview.com/pine-script-reference/v6/#fun_dayofweek) function and value of [dayofweek](https://www.tradingview.com/pine-script-reference/v6/#var_dayofweek) variable.", + "overloads": [], + "fields": [], + "type": "const int", + "remarks": "", + "example": "", + "seeAlso": [ + "const_dayofweek.sunday", + "const_dayofweek.monday", + "const_dayofweek.tuesday", + "const_dayofweek.wednesday", + "const_dayofweek.friday", + "const_dayofweek.saturday" + ] + }, + { + "id": "const_dayofweek.tuesday", + "kind": "constant", + "name": "dayofweek.tuesday", + "namespace": "dayofweek", + "description": "Is a named constant for return value of [dayofweek()](https://www.tradingview.com/pine-script-reference/v6/#fun_dayofweek) function and value of [dayofweek](https://www.tradingview.com/pine-script-reference/v6/#var_dayofweek) variable.", + "overloads": [], + "fields": [], + "type": "const int", + "remarks": "", + "example": "", + "seeAlso": [ + "const_dayofweek.sunday", + "const_dayofweek.monday", + "const_dayofweek.wednesday", + "const_dayofweek.thursday", + "const_dayofweek.friday", + "const_dayofweek.saturday" + ] + }, + { + "id": "const_dayofweek.wednesday", + "kind": "constant", + "name": "dayofweek.wednesday", + "namespace": "dayofweek", + "description": "Is a named constant for return value of [dayofweek()](https://www.tradingview.com/pine-script-reference/v6/#fun_dayofweek) function and value of [dayofweek](https://www.tradingview.com/pine-script-reference/v6/#var_dayofweek) variable.", + "overloads": [], + "fields": [], + "type": "const int", + "remarks": "", + "example": "", + "seeAlso": [ + "const_dayofweek.sunday", + "const_dayofweek.monday", + "const_dayofweek.tuesday", + "const_dayofweek.thursday", + "const_dayofweek.friday", + "const_dayofweek.saturday" + ] + }, + { + "id": "const_display.all", + "kind": "constant", + "name": "display.all", + "namespace": "display", + "description": "A named constant for use with the `display` parameter of the `plot*()`, `input*()`, [fill()](https://www.tradingview.com/pine-script-reference/v6/#fun_fill), [bgcolor()](https://www.tradingview.com/pine-script-reference/v6/#fun_bgcolor), [barcolor()](https://www.tradingview.com/pine-script-reference/v6/#fun_barcolor), and [hline()](https://www.tradingview.com/pine-script-reference/v6/#fun_hline) functions. Specifies that the values or visuals appear in all possible locations by default.", + "overloads": [], + "fields": [], + "type": "const plot_simple_display", + "remarks": "The `display.*` constants support [+](https://www.tradingview.com/pine-script-reference/v6/#op_+) and [-](https://www.tradingview.com/pine-script-reference/v6/#op_-) operations, enabling custom combinations of display settings. For example, `display.all - display.data_window` specifies that the data for an input or plot appears in all possible locations except for the Data Window.\n\nSelecting a deselected plot in the script's \"Settings/Style\" tab changes its display settings, causing the plotted data to appear in all available chart locations. To restore the display settings coded in the script, select \"Reset settings\" from the \"Defaults\" dropdown menu at the bottom of the \"Settings\" dialog box.", + "example": "", + "seeAlso": [ + "fun_plot", + "fun_plotshape", + "fun_plotchar", + "fun_plotarrow", + "fun_plotbar", + "fun_plotcandle" + ] + }, + { + "id": "const_display.data_window", + "kind": "constant", + "name": "display.data_window", + "namespace": "display", + "description": "A named constant for use with the `display` parameter of the `plot*()` and `input*()` functions. Specifies that the values are available in the Data Window by default. The Data Window tab is accessible by clicking the \"Object Tree and Data Window\" icon in the chart's right sidebar.", + "overloads": [], + "fields": [], + "type": "const plot_display", + "remarks": "The `display.*` constants support [+](https://www.tradingview.com/pine-script-reference/v6/#op_+) and [-](https://www.tradingview.com/pine-script-reference/v6/#op_-) operations, enabling custom combinations of display settings. For example, `display.data_window + display.status_line` specifies that the data for an input or plot appears in the Data Window and the script's status line, and `display.all - display.data_window` specifies that the data appears in all possible locations except for the Data Window.\n\nSelecting a deselected plot in the script's \"Settings/Style\" tab changes its display settings, causing the plotted data to appear in all available chart locations. To restore the display settings coded in the script, select \"Reset settings\" from the \"Defaults\" dropdown menu at the bottom of the \"Settings\" dialog box.", + "example": "", + "seeAlso": [ + "fun_plot", + "fun_plotshape", + "fun_plotchar", + "fun_plotarrow", + "fun_plotbar", + "fun_plotcandle" + ] + }, + { + "id": "const_display.none", + "kind": "constant", + "name": "display.none", + "namespace": "display", + "description": "A named constant for use with the `display` parameter of the `plot*()`, `input*()`, [fill()](https://www.tradingview.com/pine-script-reference/v6/#fun_fill), [bgcolor()](https://www.tradingview.com/pine-script-reference/v6/#fun_bgcolor), [barcolor()](https://www.tradingview.com/pine-script-reference/v6/#fun_barcolor), and [hline()](https://www.tradingview.com/pine-script-reference/v6/#fun_hline) functions. Specifies that the values or visuals are not displayed anywhere by default.", + "overloads": [], + "fields": [], + "type": "const plot_simple_display", + "remarks": "Selecting a deselected plot in the script's \"Settings/Style\" tab changes its display settings, causing the plotted data to appear in all available chart locations. To restore the display settings coded in the script, select \"Reset settings\" from the \"Defaults\" dropdown menu at the bottom of the \"Settings\" dialog box.", + "example": "", + "seeAlso": [ + "fun_plot", + "fun_plotshape", + "fun_plotchar", + "fun_plotarrow", + "fun_plotbar", + "fun_plotcandle" + ] + }, + { + "id": "const_display.pane", + "kind": "constant", + "name": "display.pane", + "namespace": "display", + "description": "A named constant for use with the `display` parameter of the `plot*()` functions. Specifies that the plotted values are displayed in a chart pane by default.", + "overloads": [], + "fields": [], + "type": "const plot_display", + "remarks": "The `display.*` constants support [+](https://www.tradingview.com/pine-script-reference/v6/#op_+) and [-](https://www.tradingview.com/pine-script-reference/v6/#op_-) operations, enabling custom combinations of display settings. For example, `display.pane + display.data_window` specifies that the plot's values appear in the chart pane and the Data Window, and `display.all - display.pane` specifies that the values appear in all possible locations except for the chart pane.\n\nSelecting a deselected plot in the script's \"Settings/Style\" tab changes its display settings, causing the plotted data to appear in all available chart locations. To restore the display settings coded in the script, select \"Reset settings\" from the \"Defaults\" dropdown menu at the bottom of the \"Settings\" dialog box.", + "example": "", + "seeAlso": [ + "fun_plot", + "fun_plotshape", + "fun_plotchar", + "fun_plotarrow", + "fun_plotbar", + "fun_plotcandle" + ] + }, + { + "id": "const_display.pine_screener", + "kind": "constant", + "name": "display.pine_screener", + "namespace": "display", + "description": "A named constant for use with the `display` parameter of the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function. Specifies that, by default, the [Pine Screener](https://www.tradingview.com/pine-script-reference/v6/#https://www.tradingview.com/support/solutions/43000742436/) displays a column for the plot's values when the user applies the indicator to the chosen watchlist.", + "overloads": [], + "fields": [], + "type": "const plot_display", + "remarks": "The `display.*` constants support [+](https://www.tradingview.com/pine-script-reference/v6/#op_+) and [-](https://www.tradingview.com/pine-script-reference/v6/#op_-) operations, enabling custom combinations of display settings. For example, `display.data_window + display.pine_screener` specifies that the plotted values appear in the Data Window and the Pine Screener, and `display.all - display.pine_screener` specifies that the values appear in all possible locations except for the Pine Screener.\n\nThe Pine Screener displays columns for only the first 10 enabled plots from a script by default. If a plot's default display settings do not include the screener, or if the screener already shows columns for 10 other plots from the script, users can configure the screener to show a column for the plot by using the \"Manage columns\" menu at the far right of the table header.", + "example": "", + "seeAlso": [ + "fun_plot", + "fun_plotshape", + "fun_plotchar", + "fun_plotarrow", + "fun_plotbar", + "fun_plotcandle" + ] + }, + { + "id": "const_display.price_scale", + "kind": "constant", + "name": "display.price_scale", + "namespace": "display", + "description": "A named constant for use with the `display` parameter of the `plot*()` functions. Specifies that the price scale displays a label for the plot's data, but only if the chart's settings allow it.", + "overloads": [], + "fields": [], + "type": "const plot_display", + "remarks": "The `display.*` constants support [+](https://www.tradingview.com/pine-script-reference/v6/#op_+) and [-](https://www.tradingview.com/pine-script-reference/v6/#op_-) operations, enabling custom combinations of display settings. For example, `display.price_scale + display.data_window` specifies that the plot's data appears on the price scale and in the Data Window, and `display.all - display.price_scale` specifies that the data appears in all possible locations except for the price scale.\n\nSelecting a deselected plot in the script's \"Settings/Style\" tab changes its display settings, causing the plotted data to appear in all available chart locations. To restore the display settings coded in the script, select \"Reset settings\" from the \"Defaults\" dropdown menu at the bottom of the \"Settings\" dialog box.", + "example": "", + "seeAlso": [ + "fun_plot", + "fun_plotshape", + "fun_plotchar", + "fun_plotarrow", + "fun_plotbar", + "fun_plotcandle" + ] + }, + { + "id": "const_display.status_line", + "kind": "constant", + "name": "display.status_line", + "namespace": "display", + "description": "A named constant for use with the `display` parameter of the `plot*()` and `input*()` functions. Specifies that the values are available in the script's status line, but only if the chart's settings allow it.", + "overloads": [], + "fields": [], + "type": "const plot_display", + "remarks": "The `display.*` constants support [+](https://www.tradingview.com/pine-script-reference/v6/#op_+) and [-](https://www.tradingview.com/pine-script-reference/v6/#op_-) operations, enabling custom combinations of display settings. For example, `display.data_window + display.status_line` specifies that the data for an input or plot appears in the Data Window and the script's status line, and `display.all - display.status_line` specifies that the data appears in all possible locations except for the status line.\n\nSelecting a deselected plot in the script's \"Settings/Style\" tab changes its display settings, causing the plotted data to appear in all available chart locations. To restore the display settings coded in the script, select \"Reset settings\" from the \"Defaults\" dropdown menu at the bottom of the \"Settings\" dialog box.", + "example": "", + "seeAlso": [ + "fun_plot", + "fun_plotshape", + "fun_plotchar", + "fun_plotarrow", + "fun_plotbar", + "fun_plotcandle" + ] + }, + { + "id": "const_dividends.gross", + "kind": "constant", + "name": "dividends.gross", + "namespace": "dividends", + "description": "A named constant for the [request.dividends()](https://www.tradingview.com/pine-script-reference/v6/#fun_request.dividends) function. Is used to request the dividends return on a stock before deductions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.dividends" + ] + }, + { + "id": "const_dividends.net", + "kind": "constant", + "name": "dividends.net", + "namespace": "dividends", + "description": "A named constant for the [request.dividends()](https://www.tradingview.com/pine-script-reference/v6/#fun_request.dividends) function. Is used to request the dividends return on a stock after deductions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.dividends" + ] + }, + { + "id": "const_earnings.actual", + "kind": "constant", + "name": "earnings.actual", + "namespace": "earnings", + "description": "A named constant for the [request.earnings()](https://www.tradingview.com/pine-script-reference/v6/#fun_request.earnings) function. Is used to request the earnings value as it was reported.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.earnings" + ] + }, + { + "id": "const_earnings.estimate", + "kind": "constant", + "name": "earnings.estimate", + "namespace": "earnings", + "description": "A named constant for the [request.earnings()](https://www.tradingview.com/pine-script-reference/v6/#fun_request.earnings) function. Is used to request the estimated earnings value.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.earnings" + ] + }, + { + "id": "const_earnings.standardized", + "kind": "constant", + "name": "earnings.standardized", + "namespace": "earnings", + "description": "A named constant for the [request.earnings()](https://www.tradingview.com/pine-script-reference/v6/#fun_request.earnings) function. Is used to request the standardized earnings value.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.earnings" + ] + }, + { + "id": "const_extend.both", + "kind": "constant", + "name": "extend.both", + "namespace": "extend", + "description": "A named constant for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_extend()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_extend) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_extend", + "const_extend.none", + "const_extend.left", + "const_extend.right" + ] + }, + { + "id": "const_extend.left", + "kind": "constant", + "name": "extend.left", + "namespace": "extend", + "description": "A named constant for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_extend()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_extend) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_extend", + "const_extend.none", + "const_extend.right", + "const_extend.both" + ] + }, + { + "id": "const_extend.none", + "kind": "constant", + "name": "extend.none", + "namespace": "extend", + "description": "A named constant for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_extend()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_extend) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_extend", + "const_extend.left", + "const_extend.right", + "const_extend.both" + ] + }, + { + "id": "const_extend.right", + "kind": "constant", + "name": "extend.right", + "namespace": "extend", + "description": "A named constant for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_extend()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_extend) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_extend", + "const_extend.none", + "const_extend.left", + "const_extend.both" + ] + }, + { + "id": "const_false", + "kind": "constant", + "name": "false", + "namespace": "", + "description": "Literal representing a [bool](https://www.tradingview.com/pine-script-reference/v6/#type_bool) value, and result of a comparison operation.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "See the User Manual for [comparison operators](https://www.tradingview.com/pine-script-reference/v6/#https://www.tradingview.com/pine-script-docs/language/operators/#comparison-operators) and [logical operators](https://www.tradingview.com/pine-script-reference/v6/#https://www.tradingview.com/pine-script-docs/language/operators/#logical-operators).", + "example": "", + "seeAlso": [ + "type_bool" + ] + }, + { + "id": "const_font.family_default", + "kind": "constant", + "name": "font.family_default", + "namespace": "font", + "description": "Default text font for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), [box.set_text_font_family()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_font_family), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), [label.set_text_font_family()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_text_font_family), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) and [table.cell_set_text_font_family()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell_set_text_font_family) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_box.new", + "fun_box.set_text_font_family", + "fun_label.new", + "fun_label.set_text_font_family", + "fun_table.cell", + "fun_table.cell_set_text_font_family", + "const_font.family_monospace" + ] + }, + { + "id": "const_font.family_monospace", + "kind": "constant", + "name": "font.family_monospace", + "namespace": "font", + "description": "Monospace text font for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), [box.set_text_font_family()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_font_family), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), [label.set_text_font_family()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_text_font_family), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) and [table.cell_set_text_font_family()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell_set_text_font_family) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_box.new", + "fun_box.set_text_font_family", + "fun_label.new", + "fun_label.set_text_font_family", + "fun_table.cell", + "fun_table.cell_set_text_font_family", + "const_font.family_default" + ] + }, + { + "id": "const_format.inherit", + "kind": "constant", + "name": "format.inherit", + "namespace": "format", + "description": "Is a named constant for selecting the formatting of the script output values from the parent series in the [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_indicator", + "const_format.price", + "const_format.volume", + "const_format.percent" + ] + }, + { + "id": "const_format.mintick", + "kind": "constant", + "name": "format.mintick", + "namespace": "format", + "description": "Is a named constant to use with the [str.tostring()](https://www.tradingview.com/pine-script-reference/v6/#fun_str.tostring) function. Passing a number to [str.tostring()](https://www.tradingview.com/pine-script-reference/v6/#fun_str.tostring) with this argument rounds the number to the nearest value that can be divided by [syminfo.mintick](https://www.tradingview.com/pine-script-reference/v6/#var_syminfo.mintick), without the remainder, with ties rounding up, and returns the string version of said value with trailing zeros.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_indicator", + "const_format.inherit", + "const_format.price", + "const_format.volume" + ] + }, + { + "id": "const_format.percent", + "kind": "constant", + "name": "format.percent", + "namespace": "format", + "description": "Is a named constant for selecting the formatting of the script output values as a percentage in the indicator function. It adds a percent sign after values.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "The default precision is 2, regardless of the precision of the chart itself. This can be changed with the 'precision' argument of the [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator) function.", + "example": "", + "seeAlso": [ + "fun_indicator", + "const_format.inherit", + "const_format.price", + "const_format.volume" + ] + }, + { + "id": "const_format.price", + "kind": "constant", + "name": "format.price", + "namespace": "format", + "description": "Is a named constant for selecting the formatting of the script output values as prices in the [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "If format is format.price, default precision value is set. You can use the precision argument of indicator function to change the precision value.", + "example": "", + "seeAlso": [ + "fun_indicator", + "const_format.inherit", + "const_format.volume", + "const_format.percent" + ] + }, + { + "id": "const_format.volume", + "kind": "constant", + "name": "format.volume", + "namespace": "format", + "description": "Is a named constant for selecting the formatting of the script output values as volume in the [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator) function, e.g. '5183' will be formatted as '5.183K'.\n\nThe decimal precision rules defined by this variable take precedence over other precision settings. When an [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator), [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy), or `plot*()` call uses this `format` option, the function's `precision` parameter will not affect the result.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_indicator", + "const_format.inherit", + "const_format.price", + "const_format.percent" + ] + }, + { + "id": "const_hline.style_dashed", + "kind": "constant", + "name": "hline.style_dashed", + "namespace": "hline", + "description": "Is a named constant for dashed linestyle of [hline()](https://www.tradingview.com/pine-script-reference/v6/#fun_hline) function.", + "overloads": [], + "fields": [], + "type": "const hline_style", + "remarks": "", + "example": "", + "seeAlso": [ + "const_hline.style_solid", + "const_hline.style_dotted" + ] + }, + { + "id": "const_hline.style_dotted", + "kind": "constant", + "name": "hline.style_dotted", + "namespace": "hline", + "description": "Is a named constant for dotted linestyle of [hline()](https://www.tradingview.com/pine-script-reference/v6/#fun_hline) function.", + "overloads": [], + "fields": [], + "type": "const hline_style", + "remarks": "", + "example": "", + "seeAlso": [ + "const_hline.style_solid", + "const_hline.style_dashed" + ] + }, + { + "id": "const_hline.style_solid", + "kind": "constant", + "name": "hline.style_solid", + "namespace": "hline", + "description": "Is a named constant for solid linestyle of [hline()](https://www.tradingview.com/pine-script-reference/v6/#fun_hline) function.", + "overloads": [], + "fields": [], + "type": "const hline_style", + "remarks": "", + "example": "", + "seeAlso": [ + "const_hline.style_dotted", + "const_hline.style_dashed" + ] + }, + { + "id": "const_label.style_arrowdown", + "kind": "constant", + "name": "label.style_arrowdown", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_arrowup", + "kind": "constant", + "name": "label.style_arrowup", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_circle", + "kind": "constant", + "name": "label.style_circle", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_cross", + "kind": "constant", + "name": "label.style_cross", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_diamond", + "kind": "constant", + "name": "label.style_diamond", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square" + ] + }, + { + "id": "const_label.style_flag", + "kind": "constant", + "name": "label.style_flag", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_center", + "kind": "constant", + "name": "label.style_label_center", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_down", + "kind": "constant", + "name": "label.style_label_down", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_left", + "kind": "constant", + "name": "label.style_label_left", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_lower_left", + "kind": "constant", + "name": "label.style_label_lower_left", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_lower_right", + "kind": "constant", + "name": "label.style_label_lower_right", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_right", + "kind": "constant", + "name": "label.style_label_right", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_up", + "kind": "constant", + "name": "label.style_label_up", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_upper_left", + "kind": "constant", + "name": "label.style_label_upper_left", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_label_upper_right", + "kind": "constant", + "name": "label.style_label_upper_right", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_none", + "kind": "constant", + "name": "label.style_none", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_square", + "kind": "constant", + "name": "label.style_square", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_text_outline", + "kind": "constant", + "name": "label.style_text_outline", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_triangledown", + "kind": "constant", + "name": "label.style_triangledown", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_triangleup", + "kind": "constant", + "name": "label.style_triangleup", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_xcross", + "const_label.style_cross", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_lower_left", + "const_label.style_label_lower_right", + "const_label.style_label_upper_left", + "const_label.style_label_upper_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_label.style_xcross", + "kind": "constant", + "name": "label.style_xcross", + "namespace": "label", + "description": "Label style for [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "fun_label.set_textalign", + "const_label.style_none", + "const_label.style_cross", + "const_label.style_triangleup", + "const_label.style_triangledown", + "const_label.style_flag", + "const_label.style_circle", + "const_label.style_arrowup", + "const_label.style_arrowdown", + "const_label.style_label_up", + "const_label.style_label_down", + "const_label.style_label_left", + "const_label.style_label_right", + "const_label.style_label_center", + "const_label.style_square", + "const_label.style_diamond" + ] + }, + { + "id": "const_line.style_arrow_both", + "kind": "constant", + "name": "line.style_arrow_both", + "namespace": "line", + "description": "Line style for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_style) functions. Solid line with arrows on both points.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_style", + "const_line.style_solid", + "const_line.style_dotted", + "const_line.style_dashed", + "const_line.style_arrow_left", + "const_line.style_arrow_right" + ] + }, + { + "id": "const_line.style_arrow_left", + "kind": "constant", + "name": "line.style_arrow_left", + "namespace": "line", + "description": "Line style for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_style) functions. Solid line with arrow on the first point.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_style", + "const_line.style_solid", + "const_line.style_dotted", + "const_line.style_dashed", + "const_line.style_arrow_right", + "const_line.style_arrow_both" + ] + }, + { + "id": "const_line.style_arrow_right", + "kind": "constant", + "name": "line.style_arrow_right", + "namespace": "line", + "description": "Line style for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_style) functions. Solid line with arrow on the second point.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_style", + "const_line.style_solid", + "const_line.style_dotted", + "const_line.style_dashed", + "const_line.style_arrow_left", + "const_line.style_arrow_both" + ] + }, + { + "id": "const_line.style_dashed", + "kind": "constant", + "name": "line.style_dashed", + "namespace": "line", + "description": "Line style for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_style", + "const_line.style_solid", + "const_line.style_dotted", + "const_line.style_arrow_left", + "const_line.style_arrow_right", + "const_line.style_arrow_both" + ] + }, + { + "id": "const_line.style_dotted", + "kind": "constant", + "name": "line.style_dotted", + "namespace": "line", + "description": "Line style for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_style", + "const_line.style_solid", + "const_line.style_dashed", + "const_line.style_arrow_left", + "const_line.style_arrow_right", + "const_line.style_arrow_both" + ] + }, + { + "id": "const_line.style_solid", + "kind": "constant", + "name": "line.style_solid", + "namespace": "line", + "description": "Line style for [line.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.new) and [line.set_style()](https://www.tradingview.com/pine-script-reference/v6/#fun_line.set_style) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_line.new", + "fun_line.set_style", + "const_line.style_dotted", + "const_line.style_dashed", + "const_line.style_arrow_left", + "const_line.style_arrow_right", + "const_line.style_arrow_both" + ] + }, + { + "id": "const_location.abovebar", + "kind": "constant", + "name": "location.abovebar", + "namespace": "location", + "description": "Location value for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar) functions. Shape is plotted above main series bars.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "const_location.belowbar", + "const_location.top", + "const_location.bottom", + "const_location.absolute" + ] + }, + { + "id": "const_location.absolute", + "kind": "constant", + "name": "location.absolute", + "namespace": "location", + "description": "Location value for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar) functions. Shape is plotted on chart using indicator value as a price coordinate.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "const_location.abovebar", + "const_location.belowbar", + "const_location.top", + "const_location.bottom" + ] + }, + { + "id": "const_location.belowbar", + "kind": "constant", + "name": "location.belowbar", + "namespace": "location", + "description": "Location value for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar) functions. Shape is plotted below main series bars.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "const_location.abovebar", + "const_location.top", + "const_location.bottom", + "const_location.absolute" + ] + }, + { + "id": "const_location.bottom", + "kind": "constant", + "name": "location.bottom", + "namespace": "location", + "description": "Location value for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar) functions. Shape is plotted near the bottom chart border.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "const_location.abovebar", + "const_location.belowbar", + "const_location.top", + "const_location.absolute" + ] + }, + { + "id": "const_location.top", + "kind": "constant", + "name": "location.top", + "namespace": "location", + "description": "Location value for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar) functions. Shape is plotted near the top chart border.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "const_location.abovebar", + "const_location.belowbar", + "const_location.bottom", + "const_location.absolute" + ] + }, + { + "id": "const_math.e", + "kind": "constant", + "name": "math.e", + "namespace": "math", + "description": "Is a named constant for [Euler's number](https://www.tradingview.com/pine-script-reference/v6/#https://en.wikipedia.org/wiki/E_(mathematical_constant)). It is equal to 2.7182818284590452.", + "overloads": [], + "fields": [], + "type": "const float", + "remarks": "", + "example": "", + "seeAlso": [ + "const_math.phi", + "const_math.pi", + "const_math.rphi" + ] + }, + { + "id": "const_math.phi", + "kind": "constant", + "name": "math.phi", + "namespace": "math", + "description": "Is a named constant for the [golden ratio](https://www.tradingview.com/pine-script-reference/v6/#https://en.wikipedia.org/wiki/Golden_ratio). It is equal to 1.6180339887498948.", + "overloads": [], + "fields": [], + "type": "const float", + "remarks": "", + "example": "", + "seeAlso": [ + "const_math.e", + "const_math.pi", + "const_math.rphi" + ] + }, + { + "id": "const_math.pi", + "kind": "constant", + "name": "math.pi", + "namespace": "math", + "description": "Is a named constant for [Archimedes' constant](https://www.tradingview.com/pine-script-reference/v6/#https://en.wikipedia.org/wiki/Pi). It is equal to 3.1415926535897932.", + "overloads": [], + "fields": [], + "type": "const float", + "remarks": "", + "example": "", + "seeAlso": [ + "const_math.e", + "const_math.phi", + "const_math.rphi" + ] + }, + { + "id": "const_math.rphi", + "kind": "constant", + "name": "math.rphi", + "namespace": "math", + "description": "Is a named constant for the [golden ratio conjugate](https://www.tradingview.com/pine-script-reference/v6/#https://en.wikipedia.org/wiki/Golden_ratio#Golden_ratio_conjugate). It is equal to 0.6180339887498948.", + "overloads": [], + "fields": [], + "type": "const float", + "remarks": "", + "example": "", + "seeAlso": [ + "const_math.e", + "const_math.pi", + "const_math.phi" + ] + }, + { + "id": "const_order.ascending", + "kind": "constant", + "name": "order.ascending", + "namespace": "order", + "description": "Determines the sort order of the array from the smallest to the largest value.", + "overloads": [], + "fields": [], + "type": "const sort_order", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_array.new_float", + "fun_array.sort" + ] + }, + { + "id": "const_order.descending", + "kind": "constant", + "name": "order.descending", + "namespace": "order", + "description": "Determines the sort order of the array from the largest to the smallest value.", + "overloads": [], + "fields": [], + "type": "const sort_order", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_array.new_float", + "fun_array.sort" + ] + }, + { + "id": "const_plot.linestyle_dashed", + "kind": "constant", + "name": "plot.linestyle_dashed", + "namespace": "plot", + "description": "A named constant for use with the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function's `linestyle` parameter, which modifies the appearance of plotted lines. If the `style` argument of the function call specifies a plot style that displays a line, using this constant as the `linestyle` argument specifies that the plotted line is dashed.", + "overloads": [], + "fields": [], + "type": "const plot_line_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.linestyle_solid", + "const_plot.linestyle_dotted" + ] + }, + { + "id": "const_plot.linestyle_dotted", + "kind": "constant", + "name": "plot.linestyle_dotted", + "namespace": "plot", + "description": "A named constant for use with the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function's `linestyle` parameter, which modifies the appearance of plotted lines. If the `style` argument of the function call specifies a plot style that displays a line, using this constant as the `linestyle` argument specifies that the plotted line is dotted.", + "overloads": [], + "fields": [], + "type": "const plot_line_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.linestyle_dashed", + "const_plot.linestyle_solid" + ] + }, + { + "id": "const_plot.linestyle_solid", + "kind": "constant", + "name": "plot.linestyle_solid", + "namespace": "plot", + "description": "A named constant for use with the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function's `linestyle` parameter, which modifies the appearance of plotted lines. If the `style` argument of the function call specifies a plot style that displays a line, using this constant as the `linestyle` argument specifies that the plotted line is solid.", + "overloads": [], + "fields": [], + "type": "const plot_line_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.linestyle_dashed", + "const_plot.linestyle_dotted" + ] + }, + { + "id": "const_plot.style_area", + "kind": "constant", + "name": "plot.style_area", + "namespace": "plot", + "description": "A named constant for the 'Area' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_areabr", + "kind": "constant", + "name": "plot.style_areabr", + "namespace": "plot", + "description": "A named constant for the 'Area With Breaks' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function. Similar to [plot.style_area](https://www.tradingview.com/pine-script-reference/v6/#const_plot.style_area), except the gaps in the data are not filled.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_circles", + "kind": "constant", + "name": "plot.style_circles", + "namespace": "plot", + "description": "A named constant for the 'Circles' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_columns", + "kind": "constant", + "name": "plot.style_columns", + "namespace": "plot", + "description": "A named constant for the 'Columns' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_cross", + "kind": "constant", + "name": "plot.style_cross", + "namespace": "plot", + "description": "A named constant for the 'Cross' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_histogram", + "kind": "constant", + "name": "plot.style_histogram", + "namespace": "plot", + "description": "A named constant for the 'Histogram' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_line", + "kind": "constant", + "name": "plot.style_line", + "namespace": "plot", + "description": "A named constant for the 'Line' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_linebr", + "kind": "constant", + "name": "plot.style_linebr", + "namespace": "plot", + "description": "A named constant for the 'Line With Breaks' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function. Similar to [plot.style_line](https://www.tradingview.com/pine-script-reference/v6/#const_plot.style_line), except the gaps in the data are not filled.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_stepline", + "kind": "constant", + "name": "plot.style_stepline", + "namespace": "plot", + "description": "A named constant for the 'Step Line' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline_diamond", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_stepline_diamond", + "kind": "constant", + "name": "plot.style_stepline_diamond", + "namespace": "plot", + "description": "A named constant for the 'Step Line With Diamonds' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function. Similar to [plot.style_stepline](https://www.tradingview.com/pine-script-reference/v6/#const_plot.style_stepline), except the data changes are also marked with the Diamond shapes.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_steplinebr" + ] + }, + { + "id": "const_plot.style_steplinebr", + "kind": "constant", + "name": "plot.style_steplinebr", + "namespace": "plot", + "description": "A named constant for the 'Step line with Breaks' style, to be used as an argument for the `style` parameter in the [plot()](https://www.tradingview.com/pine-script-reference/v6/#fun_plot) function.", + "overloads": [], + "fields": [], + "type": "const plot_style", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plot", + "const_plot.style_line", + "const_plot.style_linebr", + "const_plot.style_histogram", + "const_plot.style_cross", + "const_plot.style_area", + "const_plot.style_areabr", + "const_plot.style_columns", + "const_plot.style_circles", + "const_plot.style_stepline", + "const_plot.style_stepline_diamond" + ] + }, + { + "id": "const_position.bottom_center", + "kind": "constant", + "name": "position.bottom_center", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the bottom edge in the center.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_center", + "const_position.top_right", + "const_position.middle_left", + "const_position.middle_center", + "const_position.middle_right", + "const_position.bottom_left", + "const_position.bottom_right" + ] + }, + { + "id": "const_position.bottom_left", + "kind": "constant", + "name": "position.bottom_left", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the bottom left of the screen.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_center", + "const_position.top_right", + "const_position.middle_left", + "const_position.middle_center", + "const_position.middle_right", + "const_position.bottom_center", + "const_position.bottom_right" + ] + }, + { + "id": "const_position.bottom_right", + "kind": "constant", + "name": "position.bottom_right", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the bottom right of the screen.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_center", + "const_position.top_right", + "const_position.middle_left", + "const_position.middle_center", + "const_position.middle_right", + "const_position.bottom_left", + "const_position.bottom_center" + ] + }, + { + "id": "const_position.middle_center", + "kind": "constant", + "name": "position.middle_center", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the center of the screen.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_center", + "const_position.top_right", + "const_position.middle_left", + "const_position.middle_right", + "const_position.bottom_left", + "const_position.bottom_center", + "const_position.bottom_right" + ] + }, + { + "id": "const_position.middle_left", + "kind": "constant", + "name": "position.middle_left", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the left side of the screen.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_center", + "const_position.top_right", + "const_position.middle_center", + "const_position.middle_right", + "const_position.bottom_left", + "const_position.bottom_center", + "const_position.bottom_right" + ] + }, + { + "id": "const_position.middle_right", + "kind": "constant", + "name": "position.middle_right", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the right side of the screen.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_center", + "const_position.top_right", + "const_position.middle_left", + "const_position.middle_center", + "const_position.bottom_left", + "const_position.bottom_center", + "const_position.bottom_right" + ] + }, + { + "id": "const_position.top_center", + "kind": "constant", + "name": "position.top_center", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the top edge in the center.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_right", + "const_position.middle_left", + "const_position.middle_center", + "const_position.middle_right", + "const_position.bottom_left", + "const_position.bottom_center", + "const_position.bottom_right" + ] + }, + { + "id": "const_position.top_left", + "kind": "constant", + "name": "position.top_left", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the upper-left edge.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_center", + "const_position.top_right", + "const_position.middle_left", + "const_position.middle_center", + "const_position.middle_right", + "const_position.bottom_left", + "const_position.bottom_center", + "const_position.bottom_right" + ] + }, + { + "id": "const_position.top_right", + "kind": "constant", + "name": "position.top_right", + "namespace": "position", + "description": "Table position is used in [table.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.new), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) functions.\n\nBinds the table to the upper-right edge.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.new", + "fun_table.cell", + "fun_table.set_position", + "const_position.top_left", + "const_position.top_center", + "const_position.middle_left", + "const_position.middle_center", + "const_position.middle_right", + "const_position.bottom_left", + "const_position.bottom_center", + "const_position.bottom_right" + ] + }, + { + "id": "const_scale.left", + "kind": "constant", + "name": "scale.left", + "namespace": "scale", + "description": "A named constant for use as the `scale` argument in [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator) and [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy) declaration statements. Specifies that the script's price scale is on the left side of the pane. If the script overlays on the main chart pane or another script's pane, it adds a new price scale on the left side of the pane and scales its visuals independently to fit the pane's visual space.", + "overloads": [], + "fields": [], + "type": "const scale_type", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_indicator" + ] + }, + { + "id": "const_scale.none", + "kind": "constant", + "name": "scale.none", + "namespace": "scale", + "description": "A named constant for use as the `scale` argument in [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator) and [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy) declaration statements. A declaration statement can use this constant only if its `overlay` argument is `true`. Specifies that the script scales its visuals independently to fit the visual space of the main chart pane or another script's pane without displaying a separate scale. The script displays plotted numbers directly on the pane's existing price scale if the chart's settings allow it. If the user moves the script to a new pane, the script displays the values on a new scale to the left or right of that pane, depending on the chart's \"Scales placement\" setting.", + "overloads": [], + "fields": [], + "type": "const scale_type", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_indicator" + ] + }, + { + "id": "const_scale.right", + "kind": "constant", + "name": "scale.right", + "namespace": "scale", + "description": "A named constant for use as the `scale` argument in [indicator()](https://www.tradingview.com/pine-script-reference/v6/#fun_indicator) and [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy) declaration statements. Specifies that the script's price scale is on the right side of the pane. If the script overlays on the main chart pane or another script's pane, it adds a new price scale on the right side of the pane and scales its visuals independently to fit the pane's visual space.", + "overloads": [], + "fields": [], + "type": "const scale_type", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_indicator" + ] + }, + { + "id": "const_session.extended", + "kind": "constant", + "name": "session.extended", + "namespace": "session", + "description": "Constant for extended session type (with extended hours data).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "const_session.regular", + "var_syminfo.session" + ] + }, + { + "id": "const_session.regular", + "kind": "constant", + "name": "session.regular", + "namespace": "session", + "description": "Constant for regular session type (no extended hours data).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "const_session.extended", + "var_syminfo.session" + ] + }, + { + "id": "const_settlement_as_close.inherit", + "kind": "constant", + "name": "settlement_as_close.inherit", + "namespace": "settlement_as_close", + "description": "A constant to specify the value of the `settlement_as_close` parameter in [ticker.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.new) and [ticker.modify()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.modify) functions.", + "overloads": [], + "fields": [], + "type": "const settlement", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_ticker.new", + "fun_ticker.modify", + "const_settlement_as_close.on", + "const_settlement_as_close.off" + ] + }, + { + "id": "const_settlement_as_close.off", + "kind": "constant", + "name": "settlement_as_close.off", + "namespace": "settlement_as_close", + "description": "A constant to specify the value of the `settlement_as_close` parameter in [ticker.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.new) and [ticker.modify()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.modify) functions.", + "overloads": [], + "fields": [], + "type": "const settlement", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_ticker.new", + "fun_ticker.modify", + "const_settlement_as_close.on", + "const_settlement_as_close.inherit" + ] + }, + { + "id": "const_settlement_as_close.on", + "kind": "constant", + "name": "settlement_as_close.on", + "namespace": "settlement_as_close", + "description": "A constant to specify the value of the `settlement_as_close` parameter in [ticker.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.new) and [ticker.modify()](https://www.tradingview.com/pine-script-reference/v6/#fun_ticker.modify) functions.", + "overloads": [], + "fields": [], + "type": "const settlement", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_ticker.new", + "fun_ticker.modify", + "const_settlement_as_close.inherit", + "const_settlement_as_close.off" + ] + }, + { + "id": "const_shape.arrowdown", + "kind": "constant", + "name": "shape.arrowdown", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.arrowup", + "kind": "constant", + "name": "shape.arrowup", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.circle", + "kind": "constant", + "name": "shape.circle", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.cross", + "kind": "constant", + "name": "shape.cross", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.diamond", + "kind": "constant", + "name": "shape.diamond", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.flag", + "kind": "constant", + "name": "shape.flag", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.labeldown", + "kind": "constant", + "name": "shape.labeldown", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.labelup", + "kind": "constant", + "name": "shape.labelup", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.square", + "kind": "constant", + "name": "shape.square", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.triangledown", + "kind": "constant", + "name": "shape.triangledown", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.triangleup", + "kind": "constant", + "name": "shape.triangleup", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_shape.xcross", + "kind": "constant", + "name": "shape.xcross", + "namespace": "shape", + "description": "Shape style for [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape) function.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape" + ] + }, + { + "id": "const_size.auto", + "kind": "constant", + "name": "size.auto", + "namespace": "size", + "description": "A constant to specify the size of the graphics drawn by [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar), [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), and [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new). Adjusts the size of the graphics automatically.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "fun_label.set_size", + "const_size.tiny", + "const_size.small", + "const_size.normal", + "const_size.large", + "const_size.huge" + ] + }, + { + "id": "const_size.huge", + "kind": "constant", + "name": "size.huge", + "namespace": "size", + "description": "A constant to specify the size of the graphics drawn by [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar), [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), and [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell). Sets the size to huge.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "fun_label.set_size", + "const_size.auto", + "const_size.tiny", + "const_size.small", + "const_size.normal", + "const_size.large" + ] + }, + { + "id": "const_size.large", + "kind": "constant", + "name": "size.large", + "namespace": "size", + "description": "A constant to specify the size of the graphics drawn by [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar), [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), and [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell). Sets the size to large.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "fun_label.set_size", + "const_size.auto", + "const_size.tiny", + "const_size.small", + "const_size.normal", + "const_size.huge" + ] + }, + { + "id": "const_size.normal", + "kind": "constant", + "name": "size.normal", + "namespace": "size", + "description": "A constant to specify the size of the graphics drawn by [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar), [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), and [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell). Sets the size to normal.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "fun_label.set_size", + "const_size.auto", + "const_size.tiny", + "const_size.small", + "const_size.large", + "const_size.huge" + ] + }, + { + "id": "const_size.small", + "kind": "constant", + "name": "size.small", + "namespace": "size", + "description": "A constant to specify the size of the graphics drawn by [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar), [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), and [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell). Sets the size to small.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "fun_label.set_size", + "const_size.auto", + "const_size.tiny", + "const_size.normal", + "const_size.large", + "const_size.huge" + ] + }, + { + "id": "const_size.tiny", + "kind": "constant", + "name": "size.tiny", + "namespace": "size", + "description": "A constant to specify the size of the graphics drawn by [plotchar()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotchar), [plotshape()](https://www.tradingview.com/pine-script-reference/v6/#fun_plotshape), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new), [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), and [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell). Sets the size to tiny.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_plotshape", + "fun_plotchar", + "fun_label.set_size", + "const_size.auto", + "const_size.small", + "const_size.normal", + "const_size.large", + "const_size.huge" + ] + }, + { + "id": "const_splits.denominator", + "kind": "constant", + "name": "splits.denominator", + "namespace": "splits", + "description": "A named constant for the [request.splits()](https://www.tradingview.com/pine-script-reference/v6/#fun_request.splits) function. Is used to request the denominator (the number below the line in a fraction) of a splits.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.splits" + ] + }, + { + "id": "const_splits.numerator", + "kind": "constant", + "name": "splits.numerator", + "namespace": "splits", + "description": "A named constant for the [request.splits()](https://www.tradingview.com/pine-script-reference/v6/#fun_request.splits) function. Is used to request the numerator (the number above the line in a fraction) of a splits.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_request.splits" + ] + }, + { + "id": "const_strategy.cash", + "kind": "constant", + "name": "strategy.cash", + "namespace": "strategy", + "description": "This is one of the arguments that can be supplied to the `default_qty_type` parameter in the [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy) declaration statement. It is only relevant when no value is used for the ‘qty’ parameter in [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) or [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) function calls. It specifies that an amount of cash in the `strategy.account_currency` will be used to enter trades.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "//@version=6\nstrategy(\"strategy.cash\", overlay = true, default_qty_value = 50, default_qty_type = strategy.cash, initial_capital = 1000000)\n\nif bar_index == 0\n // As ‘qty’ is not defined, the previously defined values for the `default_qty_type` and `default_qty_value` parameters are used to enter trades, namely 50 units of cash in the currency of `strategy.account_currency`.\n // `qty` is calculated as (default_qty_value)/(close price). If current price is $5, then qty = 50/5 = 10.\n strategy.entry(\"EN\", strategy.long)\nif bar_index == 2\n strategy.close(\"EN\")", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_strategy.commission.cash_per_contract", + "kind": "constant", + "name": "strategy.commission.cash_per_contract", + "namespace": "strategy.commission", + "description": "Commission type for an order. Money displayed in the account currency per contract.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_strategy.commission.cash_per_order", + "kind": "constant", + "name": "strategy.commission.cash_per_order", + "namespace": "strategy.commission", + "description": "Commission type for an order. Money displayed in the account currency per order.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_strategy.commission.percent", + "kind": "constant", + "name": "strategy.commission.percent", + "namespace": "strategy.commission", + "description": "Commission type for an order. A percentage of the cash volume of order.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_strategy.direction.all", + "kind": "constant", + "name": "strategy.direction.all", + "namespace": "strategy.direction", + "description": "It allows strategy to open both long and short positions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy.risk.allow_entry_in" + ] + }, + { + "id": "const_strategy.direction.long", + "kind": "constant", + "name": "strategy.direction.long", + "namespace": "strategy.direction", + "description": "It allows strategy to open only long positions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy.risk.allow_entry_in" + ] + }, + { + "id": "const_strategy.direction.short", + "kind": "constant", + "name": "strategy.direction.short", + "namespace": "strategy.direction", + "description": "It allows strategy to open only short positions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy.risk.allow_entry_in" + ] + }, + { + "id": "const_strategy.fixed", + "kind": "constant", + "name": "strategy.fixed", + "namespace": "strategy", + "description": "This is one of the arguments that can be supplied to the `default_qty_type` parameter in the [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy) declaration statement. It is only relevant when no value is used for the ‘qty’ parameter in [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) or [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) function calls. It specifies that a number of contracts/shares/lots will be used to enter trades.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "//@version=6\nstrategy(\"strategy.fixed\", overlay = true, default_qty_value = 50, default_qty_type = strategy.fixed, initial_capital = 1000000)\n\nif bar_index == 0\n // As ‘qty’ is not defined, the previously defined values for the `default_qty_type` and `default_qty_value` parameters are used to enter trades, namely 50 contracts.\n // qty = 50\n strategy.entry(\"EN\", strategy.long)\nif bar_index == 2\n strategy.close(\"EN\")", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_strategy.long", + "kind": "constant", + "name": "strategy.long", + "namespace": "strategy", + "description": "A named constant for use with the `direction` parameter of the [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) and [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) commands. It specifies that the command creates a buy order.", + "overloads": [], + "fields": [], + "type": "const strategy_direction", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy.entry", + "fun_strategy.exit", + "fun_strategy.order" + ] + }, + { + "id": "const_strategy.oca.cancel", + "kind": "constant", + "name": "strategy.oca.cancel", + "namespace": "strategy.oca", + "description": "A named constant for use with the `oca_type` parameter of the [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) and [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) commands. It specifies that the strategy cancels the unfilled order when another order with the same `oca_name` and `oca_type` executes.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "Strategies cannot cancel or reduce pending orders from an OCA group if they execute on the same tick. For example, if the market price triggers two stop orders from [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) calls with the same `oca_*` arguments, the strategy cannot fully or partially cancel either one.", + "example": "", + "seeAlso": [ + "fun_strategy.entry", + "fun_strategy.exit", + "fun_strategy.order" + ] + }, + { + "id": "const_strategy.oca.none", + "kind": "constant", + "name": "strategy.oca.none", + "namespace": "strategy.oca", + "description": "A named constant for use with the `oca_type` parameter of the [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) and [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) commands. It specifies that the order executes independently of all other orders, including those with the same `oca_name`.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy.entry", + "fun_strategy.exit", + "fun_strategy.order" + ] + }, + { + "id": "const_strategy.oca.reduce", + "kind": "constant", + "name": "strategy.oca.reduce", + "namespace": "strategy.oca", + "description": "A named constant for use with the `oca_type` parameter of the [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) and [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) commands. It specifies that when another order with the same `oca_name` and `oca_type` executes, the strategy reduces the unfilled order by that order's size. If the unfilled order's size reaches 0 after reduction, it is the same as canceling the order entirely.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "Strategies cannot cancel or reduce pending orders from an OCA group if they execute on the same tick. For example, if the market price triggers two stop orders from [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) calls with the same `oca_*` arguments, the strategy cannot fully or partially cancel either one.\n\nOrders from [strategy.exit()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.exit) automatically use this OCA type, and they belong to the same OCA group by default.", + "example": "", + "seeAlso": [ + "fun_strategy.entry", + "fun_strategy.exit", + "fun_strategy.order" + ] + }, + { + "id": "const_strategy.percent_of_equity", + "kind": "constant", + "name": "strategy.percent_of_equity", + "namespace": "strategy", + "description": "This is one of the arguments that can be supplied to the `default_qty_type` parameter in the [strategy()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy) declaration statement. It is only relevant when no value is used for the ‘qty’ parameter in [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) or [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) function calls. It specifies that a percentage (0-100) of equity will be used to enter trades.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "//@version=6\nstrategy(\"strategy.percent_of_equity\", overlay = false, default_qty_value = 100, default_qty_type = strategy.percent_of_equity, initial_capital = 1000000)\n\n// As ‘qty’ is not defined, the previously defined values for the `default_qty_type` and `default_qty_value` parameters are used to enter trades, namely 100% of available equity.\nif bar_index == 0\n strategy.entry(\"EN\", strategy.long)\nif bar_index == 2\n strategy.close(\"EN\")\nplot(strategy.equity)\n\n // The ‘qty’ parameter is set to 10. Entering position with fixed size of 10 contracts and entry market price = (10 * close).\nif bar_index == 4\n strategy.entry(\"EN\", strategy.long, qty = 10)\nif bar_index == 6\n strategy.close(\"EN\")", + "seeAlso": [ + "fun_strategy" + ] + }, + { + "id": "const_strategy.short", + "kind": "constant", + "name": "strategy.short", + "namespace": "strategy", + "description": "A named constant for use with the `direction` parameter of the [strategy.entry()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.entry) and [strategy.order()](https://www.tradingview.com/pine-script-reference/v6/#fun_strategy.order) commands. It specifies that the command creates a sell order.", + "overloads": [], + "fields": [], + "type": "const strategy_direction", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_strategy.entry", + "fun_strategy.exit", + "fun_strategy.order" + ] + }, + { + "id": "const_text.align_bottom", + "kind": "constant", + "name": "text.align_bottom", + "namespace": "text", + "description": "Vertical text alignment for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), [box.set_text_valign()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_valign), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) and [table.cell_set_text_valign()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell_set_text_valign) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.cell", + "fun_table.cell_set_text_valign", + "const_text.align_center", + "const_text.align_left", + "const_text.align_right" + ] + }, + { + "id": "const_text.align_center", + "kind": "constant", + "name": "text.align_center", + "namespace": "text", + "description": "Text alignment for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), [box.set_text_halign()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_halign), [box.set_text_valign()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_valign), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_textalign()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_textalign) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "const_text.align_left", + "const_text.align_right" + ] + }, + { + "id": "const_text.align_left", + "kind": "constant", + "name": "text.align_left", + "namespace": "text", + "description": "Horizontal text alignment for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), [box.set_text_halign()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_halign), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_textalign()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_textalign) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "const_text.align_center", + "const_text.align_right" + ] + }, + { + "id": "const_text.align_right", + "kind": "constant", + "name": "text.align_right", + "namespace": "text", + "description": "Horizontal text alignment for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), [box.set_text_halign()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_halign), [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new) and [label.set_textalign()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.set_textalign) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_style", + "const_text.align_center", + "const_text.align_left" + ] + }, + { + "id": "const_text.align_top", + "kind": "constant", + "name": "text.align_top", + "namespace": "text", + "description": "Vertical text alignment for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new), [box.set_text_valign()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_valign), [table.cell()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell) and [table.cell_set_text_valign()](https://www.tradingview.com/pine-script-reference/v6/#fun_table.cell_set_text_valign) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_table.cell", + "fun_table.cell_set_text_valign", + "const_text.align_center", + "const_text.align_left", + "const_text.align_right" + ] + }, + { + "id": "const_text.format_bold", + "kind": "constant", + "name": "text.format_bold", + "namespace": "text", + "description": "A named constant for use with the `text_formatting` parameter of the `label.new()`, `box.new()`, `table.cell()`, and `*set_text_formatting()` functions. Makes the text bold.", + "overloads": [], + "fields": [], + "type": "const text_format", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_box.new", + "fun_table.cell" + ] + }, + { + "id": "const_text.format_italic", + "kind": "constant", + "name": "text.format_italic", + "namespace": "text", + "description": "A named constant for use with the `text_formatting` parameter of the `label.new()`, `box.new()`, `table.cell()`, and `*set_text_formatting()` functions. Italicizes the text.", + "overloads": [], + "fields": [], + "type": "const text_format", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_box.new", + "fun_table.cell" + ] + }, + { + "id": "const_text.format_none", + "kind": "constant", + "name": "text.format_none", + "namespace": "text", + "description": "A named constant for use with the `text_formatting` parameter of the `label.new()`, `box.new()`, `table.cell()`, and `*set_text_formatting()` functions. Signifies no special text formatting.", + "overloads": [], + "fields": [], + "type": "const text_format", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_box.new", + "fun_table.cell" + ] + }, + { + "id": "const_text.wrap_auto", + "kind": "constant", + "name": "text.wrap_auto", + "namespace": "text", + "description": "Automatic wrapping mode for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new) and [box.set_text_wrap()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_wrap) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_box.new", + "fun_box.set_text", + "fun_box.set_text_wrap" + ] + }, + { + "id": "const_text.wrap_none", + "kind": "constant", + "name": "text.wrap_none", + "namespace": "text", + "description": "Disabled wrapping mode for [box.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.new) and [box.set_text_wrap()](https://www.tradingview.com/pine-script-reference/v6/#fun_box.set_text_wrap) functions.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_box.new", + "fun_box.set_text", + "fun_box.set_text_wrap" + ] + }, + { + "id": "const_true", + "kind": "constant", + "name": "true", + "namespace": "", + "description": "Literal representing one of the values a [bool](https://www.tradingview.com/pine-script-reference/v6/#type_bool) variable can hold, or an expression can evaluate to when it uses comparison or logical operators.", + "overloads": [], + "fields": [], + "type": null, + "remarks": "See the User Manual for [comparison operators](https://www.tradingview.com/pine-script-reference/v6/#https://www.tradingview.com/pine-script-docs/language/operators/#comparison-operators) and [logical operators](https://www.tradingview.com/pine-script-reference/v6/#https://www.tradingview.com/pine-script-docs/language/operators/#logical-operators).", + "example": "", + "seeAlso": [ + "type_bool" + ] + }, + { + "id": "const_xloc.bar_index", + "kind": "constant", + "name": "xloc.bar_index", + "namespace": "xloc", + "description": "A constant that specifies how functions that create and modify Pine drawings interpret x-coordinates. If `xloc = xloc.bar_index`, the drawing object treats each x-coordinate as a `bar_index` value.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "const_xloc.bar_time", + "fun_line.new", + "fun_label.new", + "fun_box.new", + "fun_polyline.new", + "fun_line.set_xloc", + "fun_label.set_xloc" + ] + }, + { + "id": "const_xloc.bar_time", + "kind": "constant", + "name": "xloc.bar_time", + "namespace": "xloc", + "description": "A constant that specifies how functions that create and modify Pine drawings interpret x-coordinates. If `xloc = xloc.bar_time`, the drawing object treats each x-coordinate as a UNIX timestamp, expressed in milliseconds.", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "const_xloc.bar_index", + "fun_line.new", + "fun_label.new", + "fun_box.new", + "fun_polyline.new", + "fun_line.set_xloc", + "fun_label.set_xloc", + "const_xloc.bar_index" + ] + }, + { + "id": "const_yloc.abovebar", + "kind": "constant", + "name": "yloc.abovebar", + "namespace": "yloc", + "description": "A named constant that specifies the algorithm of interpretation of y-value in function [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_yloc", + "const_yloc.price", + "const_yloc.belowbar" + ] + }, + { + "id": "const_yloc.belowbar", + "kind": "constant", + "name": "yloc.belowbar", + "namespace": "yloc", + "description": "A named constant that specifies the algorithm of interpretation of y-value in function [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_yloc", + "const_yloc.price", + "const_yloc.abovebar" + ] + }, + { + "id": "const_yloc.price", + "kind": "constant", + "name": "yloc.price", + "namespace": "yloc", + "description": "A named constant that specifies the algorithm of interpretation of y-value in function [label.new()](https://www.tradingview.com/pine-script-reference/v6/#fun_label.new).", + "overloads": [], + "fields": [], + "type": "const string", + "remarks": "", + "example": "", + "seeAlso": [ + "fun_label.new", + "fun_label.set_yloc", + "const_yloc.abovebar", + "const_yloc.belowbar" + ] + }, + { + "id": "fun_alert", + "kind": "function", + "name": "alert", + "namespace": "", + "description": "Creates an alert trigger for an indicator or strategy, with a specified frequency, when called on the latest realtime bar. To activate alerts for a script containing calls to this function, open the \"Create Alert\" dialog box, then select the script name and \"Any alert() function call\" in the \"Condition\" section.", + "overloads": [ + { + "syntax": "alert(message, freq) → void", + "params": [ + { + "name": "message", + "type": "series string", + "description": "The message to send when the alert occurs.", + "optional": false, + "default": null + }, + { + "name": "freq", + "type": "input string", + "description": "Optional. Determines the allowed frequency of the alert trigger. Possible values are: [alert.freq_all](https://www.tradingview.com/pine-script-reference/v6/#const_alert.freq_all) (allows an alert on any realtime update), [alert.freq_once_per_bar](https://www.tradingview.com/pine-script-reference/v6/#const_alert.freq_once_per_bar) (allows an alert only on the first execution for each realtime bar), or [alert.freq_once_per_bar_close](https://www.tradingview.com/pine-script-reference/v6/#const_alert.freq_once_per_bar_close) (allows an alert only when a realtime bar closes). The default is [alert.freq_once_per_bar](https://www.tradingview.com/pine-script-reference/v6/#const_alert.freq_once_per_bar).", + "optional": true, + "default": "[alert" + } + ], + "returns": { + "type": "void", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "The `alert()` function does not display information on the chart.\n\nIn contrast to [alertcondition()](https://www.tradingview.com/pine-script-reference/v6/#fun_alertcondition), calls to this function do not count toward a script's plot count. Additionally, `alert()` calls are allowed in local scopes, including the scopes of exported library functions.\n\nSee [this article](https://www.tradingview.com/pine-script-reference/v6/#https://www.tradingview.com/chart/?solution=43000597494) in our Help Center to learn more about activating alerts from `alert()` calls.", + "example": "//@version=6\nindicator(\"`alert()` example\", \"\", true)\nma = ta.sma(close, 14)\nxUp = ta.crossover(close, ma)\nif xUp\n // Trigger the alert the first time a cross occurs during the real-time bar.\n alert(\"Price (\" + str.tostring(close) + \") crossed over MA (\" + str.tostring(ma) + \").\", alert.freq_once_per_bar)\nplot(ma)\nplotchar(xUp, \"xUp\", \"▲\", location.top, size = size.tiny)", + "seeAlso": [ + "fun_alertcondition" + ] + }, + { + "id": "fun_alertcondition", + "kind": "function", + "name": "alertcondition", + "namespace": "", + "description": "Creates alert condition, that is available in Create Alert dialog. Please note, that [alertcondition()](https://www.tradingview.com/pine-script-reference/v6/#fun_alertcondition) does NOT create an alert, it just gives you more options in Create Alert dialog. Also, [alertcondition()](https://www.tradingview.com/pine-script-reference/v6/#fun_alertcondition) effect is invisible on chart.", + "overloads": [ + { + "syntax": "alertcondition(condition, title, message) → void", + "params": [ + { + "name": "condition", + "type": "series bool", + "description": "Series of boolean values that is used for alert. True values mean alert fire, false - no alert. Required argument.", + "optional": false, + "default": null + }, + { + "name": "title", + "type": "const string", + "description": "Title of the alert condition. Optional argument.", + "optional": true, + "default": null + }, + { + "name": "message", + "type": "const string", + "description": "Message to display when alert fires. Optional argument.", + "optional": true, + "default": null + } + ], + "returns": { + "type": "void", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "Please note that an alertcondition call generates an additional plot. All such calls are taken into account when we calculate the number of the output series per script.", + "example": "//@version=6\nindicator(\"alertcondition\", overlay=true)\nalertcondition(close >= open, title='Alert on Green Bar', message='Green Bar!')", + "seeAlso": [ + "fun_alert" + ] + }, + { + "id": "fun_array.abs", + "kind": "function", + "name": "array.abs", + "namespace": "array", + "description": "Returns an array containing the absolute value of each element in the original array.", + "overloads": [ + { + "syntax": "array.abs(id) → array", + "params": [ + { + "name": "id", + "type": "array", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "array", + "description": "" + } + }, + { + "syntax": "array.abs(id) → array", + "params": [ + { + "name": "id", + "type": "array", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "array", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "", + "example": "", + "seeAlso": [ + "fun_array.new_float", + "fun_array.insert", + "fun_array.slice", + "fun_array.reverse", + "const_order.ascending", + "const_order.descending" + ] + }, + { + "id": "fun_array.avg", + "kind": "function", + "name": "array.avg", + "namespace": "array", + "description": "The function returns the mean of an array's elements.", + "overloads": [ + { + "syntax": "array.avg(id) → series float", + "params": [ + { + "name": "id", + "type": "array", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "series float", + "description": "Mean of array's elements." + } + }, + { + "syntax": "array.avg(id) → series int", + "params": [ + { + "name": "id", + "type": "array", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "series int", + "description": "Mean of array's elements." + } + } + ], + "fields": [], + "type": null, + "remarks": "Returns [na](https://www.tradingview.com/pine-script-reference/v6/#var_na) if the `id` array is empty.", + "example": "//@version=6\nindicator(\"array.avg example\")\na = array.new_float(0)\nfor i = 0 to 9\n array.push(a, close[i])\nplot(array.avg(a))", + "seeAlso": [ + "fun_array.new_float", + "fun_array.max", + "fun_array.min", + "fun_array.stdev" + ] + }, + { + "id": "fun_array.binary_search", + "kind": "function", + "name": "array.binary_search", + "namespace": "array", + "description": "Performs a [binary search](https://www.tradingview.com/pine-script-reference/v6/#https://en.wikipedia.org/wiki/Binary_search) through a sorted array to locate an element corresponding to a target value. The function returns an element's index if the value from that element equals the target value. Otherwise, it returns -1.", + "overloads": [ + { + "syntax": "array.binary_search(id, val) → series int", + "params": [ + { + "name": "id", + "type": "array", + "description": "The ID of the array to search. The function can search an array of \"int\" or \"float\" values, or an array with elements of any user-defined type that contains at least one \"int\" or \"float\" field. The array's elements must be sorted in ascending order by numeric value for correct results.", + "optional": false, + "default": null + }, + { + "name": "val", + "type": "series int/float", + "description": "The target value to locate in the array. If the array contains elements of the \"int\" or \"float\" type, the function searches the elements for the value directly. If the array contains elements of a user-defined type, the function searches for the value in the specified \"int\" or \"float\" field from the objects referenced by the array's elements.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "series int", + "description": "" + } + }, + { + "syntax": "array.binary_search(id, val, sort_field) → series int", + "params": [ + { + "name": "id", + "type": "any array type", + "description": "The ID of the array to search. The function can search an array of \"int\" or \"float\" values, or an array with elements of any user-defined type that contains at least one \"int\" or \"float\" field. The array's elements must be sorted in ascending order by numeric value for correct results.", + "optional": false, + "default": null + }, + { + "name": "val", + "type": "series int/float/string", + "description": "The target value to locate in the array. If the array contains elements of the \"int\" or \"float\" type, the function searches the elements for the value directly. If the array contains elements of a user-defined type, the function searches for the value in the specified \"int\" or \"float\" field from the objects referenced by the array's elements.", + "optional": false, + "default": null + }, + { + "name": "sort_field", + "type": "const int/string", + "description": "Optional. If the array contains elements of a user-defined type, this parameter specifies which of the type's fields the function uses to search the elements. The function can search the array using any of the type's \"int\" or \"float\" fields. An \"int\" `sort_field` argument specifies a field by its index, where 0 refers to the first field in the type declaration. A \"string\" argument specifies a field by its name. For correct results, the array's elements must be sorted in ascending order by the specified field. The default is 0.", + "optional": true, + "default": "0" + } + ], + "returns": { + "type": "series int", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "Unlike [array.indexof()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.indexof) and [array.lastindexof()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.lastindexof), this function searches an array by repeatedly checking the middle element in the index range and dividing the search range in half. First, it checks if the value from the element at the middle index equals the target value, then returns that index immediately if the two values are equal. If the values are not equal, the function then reduces the search range to the first half of the current range if the target value is less than the middle value, and to the second half otherwise. This process repeats until the function either finds an element corresponding to the target value or reduces the search range to a single index.\n\nIf a sorted array contains multiple elements whose values match the target value, the index returned by this function does not necessarily correspond to the first or last occurrence of that value. To perform a binary search on an array and then retrieve the index for the first or last occurrence of a value, use the [array.binary_search_leftmost()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.binary_search_leftmost) or [array.binary_search_rightmost()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.binary_search_rightmost) function, respectively.", + "example": "//@version=6\nindicator(\"array.binary_search\")\na = array.from(5, -2, 0, 9, 1)\narray.sort(a) // [-2, 0, 1, 5, 9]\nposition = array.binary_search(a, 0) // 1\nplot(position)", + "seeAlso": [ + "fun_array.new_float", + "fun_array.insert", + "fun_array.slice", + "fun_array.reverse", + "const_order.ascending", + "const_order.descending" + ] + }, + { + "id": "fun_array.binary_search_leftmost", + "kind": "function", + "name": "array.binary_search_leftmost", + "namespace": "array", + "description": "Performs a [binary search](https://www.tradingview.com/pine-script-reference/v6/#https://en.wikipedia.org/wiki/Binary_search) through a sorted array to locate an element corresponding to a target value. If the function locates the target value in the array's elements or referenced object fields, it returns the index of the first element whose retrieved value equals that value. Otherwise, it returns the index of the last element whose retrieved value is less than the target value, or 0 if the target value is less than the value from the first element.", + "overloads": [ + { + "syntax": "array.binary_search_leftmost(id, val) → series int", + "params": [ + { + "name": "id", + "type": "array", + "description": "The ID of the array to search. The function can search an array of \"int\" or \"float\" values, or an array with elements of any user-defined type that contains at least one \"int\" or \"float\" field. The array's elements must be sorted in ascending order by numeric value for correct results.", + "optional": false, + "default": null + }, + { + "name": "val", + "type": "series int/float", + "description": "The target value to locate in the array. If the array contains elements of the \"int\" or \"float\" type, the function searches the elements for the value directly. If the array contains elements of a user-defined type, the function searches for the value in the specified \"int\" or \"float\" field from the objects referenced by the array's elements.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "series int", + "description": "" + } + }, + { + "syntax": "array.binary_search_leftmost(id, val, sort_field) → series int", + "params": [ + { + "name": "id", + "type": "any array type", + "description": "The ID of the array to search. The function can search an array of \"int\" or \"float\" values, or an array with elements of any user-defined type that contains at least one \"int\" or \"float\" field. The array's elements must be sorted in ascending order by numeric value for correct results.", + "optional": false, + "default": null + }, + { + "name": "val", + "type": "series int/float/string", + "description": "The target value to locate in the array. If the array contains elements of the \"int\" or \"float\" type, the function searches the elements for the value directly. If the array contains elements of a user-defined type, the function searches for the value in the specified \"int\" or \"float\" field from the objects referenced by the array's elements.", + "optional": false, + "default": null + }, + { + "name": "sort_field", + "type": "const int/string", + "description": "Optional. If the array contains elements of a user-defined type, this parameter specifies which of the type's fields the function uses to search the elements. The function can search the array using any of the type's \"int\" or \"float\" fields. An \"int\" `sort_field` argument specifies a field by its index, where 0 refers to the first field in the type declaration. A \"string\" argument specifies a field by its name. For correct results, the array's elements must be sorted in ascending order by the specified field. The default is 0.", + "optional": true, + "default": "0" + } + ], + "returns": { + "type": "series int", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "Unlike [array.indexof()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.indexof) and [array.lastindexof()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.lastindexof), this function searches an array by repeatedly checking the middle element in the index range and dividing the search range in half. First, it checks if the value from the element at the middle index equals the target value, then returns that index immediately if the two values are equal. If the values are not equal, the function then reduces the search range to the first half of the current range if the target value is less than the middle value, and to the second half otherwise. This process repeats until the function either finds an element corresponding to the target value or reduces the search range to a single index.", + "example": "//@version=6\nindicator(\"array.binary_search_leftmost\")\na = array.from(5, -2, 0, 9, 1)\narray.sort(a) // [-2, 0, 1, 5, 9]\nposition = array.binary_search_leftmost(a, 3) // 2\nplot(position)\n\n//@version=6\nindicator(\"array.binary_search_leftmost, repetitive elements\")\na = array.from(4, 5, 5, 5)\n// Returns the index of the first instance.\nposition = array.binary_search_leftmost(a, 5)\nplot(position) // Plots 1", + "seeAlso": [ + "fun_array.new_float", + "fun_array.insert", + "fun_array.slice", + "fun_array.reverse", + "const_order.ascending", + "const_order.descending" + ] + }, + { + "id": "fun_array.binary_search_rightmost", + "kind": "function", + "name": "array.binary_search_rightmost", + "namespace": "array", + "description": "Performs a [binary search](https://www.tradingview.com/pine-script-reference/v6/#https://en.wikipedia.org/wiki/Binary_search) through a sorted array to locate an element corresponding to a target value. If the function locates the target value in the array's elements or referenced object fields, it returns the index of the last element whose retrieved value equals that value. Otherwise, it returns the index of the first element whose retrieved value is greater than the target value, or the array's last index plus one if the target value is greater than the value from the last element.", + "overloads": [ + { + "syntax": "array.binary_search_rightmost(id, val) → series int", + "params": [ + { + "name": "id", + "type": "array", + "description": "The ID of the array to search. The function can search an array of \"int\" or \"float\" values, or an array with elements of any user-defined type that contains at least one \"int\" or \"float\" field. The array's elements must be sorted in ascending order by numeric value for correct results.", + "optional": false, + "default": null + }, + { + "name": "val", + "type": "series int/float", + "description": "The target value to locate in the array. If the array contains elements of the \"int\" or \"float\" type, the function searches the elements for the value directly. If the array contains elements of a user-defined type, the function searches for the value in the specified \"int\" or \"float\" field from the objects referenced by the array's elements.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "series int", + "description": "" + } + }, + { + "syntax": "array.binary_search_rightmost(id, val, sort_field) → series int", + "params": [ + { + "name": "id", + "type": "any array type", + "description": "The ID of the array to search. The function can search an array of \"int\" or \"float\" values, or an array with elements of any user-defined type that contains at least one \"int\" or \"float\" field. The array's elements must be sorted in ascending order by numeric value for correct results.", + "optional": false, + "default": null + }, + { + "name": "val", + "type": "series int/float/string", + "description": "The target value to locate in the array. If the array contains elements of the \"int\" or \"float\" type, the function searches the elements for the value directly. If the array contains elements of a user-defined type, the function searches for the value in the specified \"int\" or \"float\" field from the objects referenced by the array's elements.", + "optional": false, + "default": null + }, + { + "name": "sort_field", + "type": "const int/string", + "description": "Optional. If the array contains elements of a user-defined type, this parameter specifies which of the type's fields the function uses to search the elements. The function can search the array using any of the type's \"int\" or \"float\" fields. An \"int\" `sort_field` argument specifies a field by its index, where 0 refers to the first field in the type declaration. A \"string\" argument specifies a field by its name. For correct results, the array's elements must be sorted in ascending order by the specified field. The default is 0.", + "optional": true, + "default": "0" + } + ], + "returns": { + "type": "series int", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "Unlike [array.indexof()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.indexof) and [array.lastindexof()](https://www.tradingview.com/pine-script-reference/v6/#fun_array.lastindexof), this function searches an array by repeatedly checking the middle element in the index range and dividing the search range in half. First, it checks if the value from the element at the middle index equals the target value, then returns that index immediately if the two values are equal. If the values are not equal, the function then reduces the search range to the first half of the current range if the target value is less than the middle value, and to the second half otherwise. This process repeats until the function either finds an element corresponding to the target value or reduces the search range to a single index.", + "example": "//@version=6\nindicator(\"array.binary_search_rightmost\")\na = array.from(5, -2, 0, 9, 1)\narray.sort(a) // [-2, 0, 1, 5, 9]\nposition = array.binary_search_rightmost(a, 3) // 3\nplot(position)\n\n//@version=6\nindicator(\"array.binary_search_rightmost, repetitive elements\")\na = array.from(4, 5, 5, 5)\n// Returns the index of the last instance.\nposition = array.binary_search_rightmost(a, 5)\nplot(position) // Plots 3", + "seeAlso": [ + "fun_array.new_float", + "fun_array.insert", + "fun_array.slice", + "fun_array.reverse", + "const_order.ascending", + "const_order.descending" + ] + }, + { + "id": "fun_array.clear", + "kind": "function", + "name": "array.clear", + "namespace": "array", + "description": "The function removes all elements from an array.", + "overloads": [ + { + "syntax": "array.clear(id) → void", + "params": [ + { + "name": "id", + "type": "any array type", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "void", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"array.clear example\")\na = array.new_float(5,high)\narray.clear(a)\narray.push(a, close)\nplot(array.get(a,0))\nplot(array.size(a))", + "seeAlso": [ + "fun_array.new_float", + "fun_array.insert", + "fun_array.push", + "fun_array.remove", + "fun_array.pop" + ] + }, + { + "id": "fun_array.concat", + "kind": "function", + "name": "array.concat", + "namespace": "array", + "description": "The function is used to merge two arrays. It pushes all elements from the second array to the first array, and returns the first array.", + "overloads": [ + { + "syntax": "array.concat(id1, id2) → array", + "params": [ + { + "name": "id1", + "type": "any array type", + "description": "The first array object.", + "optional": false, + "default": null + }, + { + "name": "id2", + "type": "any array type", + "description": "The second array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "array", + "description": "The first array with merged elements from the second array." + } + } + ], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"array.concat example\")\na = array.new_float(0,0)\nb = array.new_float(0,0)\nfor i = 0 to 4\n array.push(a, high[i])\n array.push(b, low[i])\nc = array.concat(a,b)\nplot(array.size(a))\nplot(array.size(b))\nplot(array.size(c))", + "seeAlso": [ + "fun_array.new_float", + "fun_array.insert", + "fun_array.slice" + ] + }, + { + "id": "fun_array.copy", + "kind": "function", + "name": "array.copy", + "namespace": "array", + "description": "The function creates a copy of an existing array.", + "overloads": [ + { + "syntax": "array.copy(id) → array", + "params": [ + { + "name": "id", + "type": "any array type", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "array", + "description": "A copy of an array." + } + } + ], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"array.copy example\")\nlength = 5\na = array.new_float(length, close)\nb = array.copy(a)\na := array.new_float(length, open)\nplot(array.sum(a) / length)\nplot(array.sum(b) / length)", + "seeAlso": [ + "fun_array.new_float", + "fun_array.get", + "fun_array.slice", + "fun_array.sort" + ] + }, + { + "id": "fun_array.covariance", + "kind": "function", + "name": "array.covariance", + "namespace": "array", + "description": "The function returns the covariance of two arrays.", + "overloads": [ + { + "syntax": "array.covariance(id1, id2, biased) → series float", + "params": [ + { + "name": "id1", + "type": "array", + "description": "An array object.", + "optional": false, + "default": null + }, + { + "name": "id2", + "type": "array", + "description": "An array object.", + "optional": false, + "default": null + }, + { + "name": "biased", + "type": "series bool", + "description": "Determines which estimate should be used. Optional. The default is true.", + "optional": true, + "default": "true" + } + ], + "returns": { + "type": "series float", + "description": "The covariance of two arrays." + } + } + ], + "fields": [], + "type": null, + "remarks": "If `biased` is [true](https://www.tradingview.com/pine-script-reference/v6/#const_true), function will calculate using a biased estimate of the entire population, if [false](https://www.tradingview.com/pine-script-reference/v6/#const_false) - unbiased estimate of a sample. Returns [na](https://www.tradingview.com/pine-script-reference/v6/#var_na) if both arrays are empty.", + "example": "//@version=6\nindicator(\"array.covariance example\")\na = array.new_float(0)\nb = array.new_float(0)\nfor i = 0 to 9\n array.push(a, close[i])\n array.push(b, open[i])\nplot(array.covariance(a, b))", + "seeAlso": [ + "fun_array.new_float", + "fun_array.max", + "fun_array.stdev", + "fun_array.avg", + "fun_array.variance" + ] + }, + { + "id": "fun_array.every", + "kind": "function", + "name": "array.every", + "namespace": "array", + "description": "Returns [true](https://www.tradingview.com/pine-script-reference/v6/#const_true) if all elements of the `id` array are [true](https://www.tradingview.com/pine-script-reference/v6/#const_true), [false](https://www.tradingview.com/pine-script-reference/v6/#const_false) otherwise.", + "overloads": [ + { + "syntax": "array.every(id) → series bool", + "params": [ + { + "name": "id", + "type": "array", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "series bool", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "This function also works with arrays of [int](https://www.tradingview.com/pine-script-reference/v6/#type_int) and [float](https://www.tradingview.com/pine-script-reference/v6/#type_float) types, in which case zero values are considered [false](https://www.tradingview.com/pine-script-reference/v6/#const_false), and all others [true](https://www.tradingview.com/pine-script-reference/v6/#const_true).", + "example": "", + "seeAlso": [ + "fun_array.some", + "fun_array.get" + ] + }, + { + "id": "fun_array.fill", + "kind": "function", + "name": "array.fill", + "namespace": "array", + "description": "The function sets elements of an array to a single value. If no index is specified, all elements are set. If only a start index (default 0) is supplied, the elements starting at that index are set. If both index parameters are used, the elements from the starting index up to but not including the end index (default na) are set.", + "overloads": [ + { + "syntax": "array.fill(id, value, index_from, index_to) → void", + "params": [ + { + "name": "id", + "type": "any array type", + "description": "An array object.", + "optional": false, + "default": null + }, + { + "name": "value", + "type": "series ", + "description": "Value to fill the array with.", + "optional": false, + "default": null + }, + { + "name": "index_from", + "type": "series int", + "description": "Start index, default is 0.", + "optional": false, + "default": "0" + }, + { + "name": "index_to", + "type": "series int", + "description": "End index, default is na. Must be one greater than the index of the last element to set.", + "optional": false, + "default": "na" + } + ], + "returns": { + "type": "void", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"array.fill example\")\na = array.new_float(10)\narray.fill(a, close)\nplot(array.sum(a))", + "seeAlso": [ + "fun_array.new_float", + "fun_array.set", + "fun_array.slice" + ] + }, + { + "id": "fun_array.first", + "kind": "function", + "name": "array.first", + "namespace": "array", + "description": "Returns the array's first element. Throws a runtime error if the array is empty.", + "overloads": [ + { + "syntax": "array.first(id) → series ", + "params": [ + { + "name": "id", + "type": "any array type", + "description": "An array object.", + "optional": false, + "default": null + } + ], + "returns": { + "type": "series ", + "description": "" + } + } + ], + "fields": [], + "type": null, + "remarks": "", + "example": "//@version=6\nindicator(\"array.first example\")\narr = array.new_int(3, 10)\nplot(array.first(arr))", + "seeAlso": [ + "fun_array.last", + "fun_array.get" + ] + }, + { + "id": "fun_array.from", + "kind": "function", + "name": "array.from", + "namespace": "array", + "description": "The function takes a variable number of arguments with one of the types: int, float, bool, string, label, line, color, box, table, linefill, and returns an array of the corresponding type.", + "overloads": [ + { + "syntax": "array.from(arg0, arg1, ...) → array", + "params": [], + "returns": { + "type": "array", + "description": "The array element's value." + } + }, + { + "syntax": "array.from(arg0, arg1, ...) → array", + "params": [], + "returns": { + "type": "array", + "description": "The array element's value." + } + }, + { + "syntax": "array.from(arg0, arg1, ...) → array