Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

code-fns

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

code-fns - npm Package Compare versions

Comparing version 0.2.3 to 0.3.0

coverage/base.css

65

lib/code.d.ts
import type { Root } from 'hast';
export declare function getColor(classList: string[]): string | undefined;
export declare type Parsable = [string, string] | {

@@ -6,7 +7,40 @@ lang: string;

};
export interface Parsed<T extends Char> {
export interface Parsed<T extends Char = Char, L extends Line = Line> {
language: string;
lines: L[];
chars: T[];
}
export declare function tokenColors(code: Parsed<Char> | Parsable): [string, [number, number], string?][];
export interface Tokenized<T extends Token = Token, L extends Line = Line> {
language: string;
lines: L[];
tokens: T[];
}
export interface Line {
tags: string[];
number?: number;
}
export interface Text {
classList: string[];
sections: string[];
color?: string;
background?: string;
provinance?: 'create' | 'retain' | 'delete';
}
export interface Char extends Text {
char: string;
token: [number, number];
}
export interface Token extends Text {
token: string;
prior?: [number, number];
location: [number, number];
}
export declare function parse(language: string, code: string): Parsed<Char>;
export declare const tagRegex: RegExp;
/**
* @internal
* @param input - parsed or parsable variable
* @returns parsed code
*/
export declare function ensureParsed(input: Parsed | Parsable): Parsed;
export declare function ready(): Promise<{

@@ -19,21 +53,8 @@ flagToScope: (flag: string) => string | undefined;

export declare function toString(code: Parsed<Char> | Parsable): string;
export declare function parse(language: string, code: string): Parsed<Char>;
export interface Char {
char: string;
classList: string[];
token: [number, number];
}
export interface RepChar extends Char {
from: 'new' | 'old';
}
export interface FormChar extends Char {
from: 'create' | 'keep' | 'delete';
}
export declare function substitute(code: Parsed<Char> | Parsable, subs: Record<string, string>): Parsed<RepChar>;
export declare function transform(code: Parsed<Char> | Parsable, start: Record<string, string>, final: Record<string, string>): FormChar[];
export interface Transition {
delete: [string, [number, number], string?][];
create: [string, [number, number], string?][];
retain: [string, [number, number], [number, number], string?][];
}
export declare function transition(code: Parsed<Char> | Parsable, start: Record<string, string>, final: Record<string, string>): Transition;
export declare function getSpan(tree: Char[], at: number): string;
/**
* Removes all code-fns tags.
*
* @param code - the parsed or parsable code to clean
*/
export declare function clean(code: Parsed<Char> | Parsable): Parsed<Char>;

@@ -6,64 +6,18 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.transition = exports.transform = exports.substitute = exports.parse = exports.toString = exports.ready = exports.tokenColors = void 0;
exports.clean = exports.getSpan = exports.toString = exports.ready = exports.ensureParsed = exports.tagRegex = exports.parse = exports.getColor = void 0;
const starry_night_1 = require("@wooorm/starry-night");
const dark_style_json_1 = __importDefault(require("./dark-style.json"));
const rules = new Map(Object.entries(dark_style_json_1.default).map(([k, v]) => [k, new Map(Object.entries(v))]));
function ensureParsed(input) {
if (Array.isArray(input)) {
return parse(input[0], input[1]);
}
else if ('code' in input) {
return parse(input.lang, input.code);
}
else {
return input;
}
function getColor(classList) {
console.assert(classList.length <= 1, `classList too long`);
const styles = classList.length === 1 ? rules.get(`.${classList[0]}`) : new Map();
console.assert((styles?.size ?? 0) <= 1, `more styles than just color`);
const color = styles?.get('color');
return color;
}
function tokenColors(code) {
const input = ensureParsed(code).chars;
const result = [];
let lastColor = Symbol();
let [ln, at] = [0, 0];
for (let i = 0; i < input.length; i++) {
const classList = input[i].classList;
console.assert(classList.length <= 1, `classList too long`);
const styles = classList.length === 1 ? rules.get(`.${classList[0]}`) : new Map();
console.assert((styles?.size ?? 0) <= 1, `more styles than just color`);
const color = styles?.get('color');
if (input[i].char === '\n') {
lastColor = Symbol();
ln++;
at = 0;
}
else if (color === lastColor) {
last(result)[0] += input[i].char;
at++;
}
else {
const char = input[i].char;
result.push(color ? [char, [ln, at], color] : [char, [ln, at]]);
at++;
}
lastColor = color;
}
return result;
}
exports.tokenColors = tokenColors;
let starryNight = null;
const starryNightPromise = (0, starry_night_1.createStarryNight)(starry_night_1.all);
starryNightPromise.then((sn) => (starryNight = sn));
function ready() {
return starryNightPromise;
}
exports.ready = ready;
function toString(code) {
const parsed = ensureParsed(code);
const result = [];
parsed.chars.forEach(({ char }) => result.push(char));
return result.join('');
}
exports.toString = toString;
exports.getColor = getColor;
function parse(language, code) {
if (starryNight == null)
if (starryNight == null) {
throw new Error('you must await ready() to initialize package');
}
const scope = starryNight.flagToScope(language);

@@ -75,13 +29,9 @@ if (typeof scope !== 'string') {

const converted = recurse(parsed);
return {
return markTree({
language,
lines: [],
chars: converted,
};
});
}
exports.parse = parse;
function last(arr) {
if (arr.length === 0)
arr.push([]);
return arr[arr.length - 1];
}
function recurse(node, classes = [], result = []) {

@@ -106,2 +56,3 @@ if (node.type === 'element') {

token: [i, node.value.length],
sections: [],
});

@@ -112,3 +63,126 @@ }

}
const tagRegex = /^\/\*<[^\S\r\n]*(.*?)[^\S\r\n]*>\*\/$/;
const nextLineRegex = /^\/\/:[^\S\n]*next-line[^\S\n]+([^\n]+?)[^\S\n]*$/;
const thisLineRegex = /^\/\/:[^\S\n]*this-line[^\S\n]+([^\n]+?)[^\S\n]*$/;
const blockStartRegex = /^\/\/<<[^\S\n]*([^\n]+?)[^\S\n]*$/;
const blockEndRegex = /^\/\/>>[^\S\n]*$/;
const sectionStartRegex = /^\/\*<<[^\S\n]*([^\n]+?)[^\S\n]*\*\/$/;
const sectionEndRegex = /^\/\*>>[^\S\n]*\*\/$/;
exports.tagRegex = /^\/\*<[^\S\r\n]*(.*?)[^\S\r\n]*>\*\/$/;
const specialTypes = [
[nextLineRegex, ['nextLine', 'line']],
[thisLineRegex, ['thisLine', 'line']],
[blockStartRegex, ['blockStart', 'line']],
[blockEndRegex, ['blockEnd', 'line']],
[sectionStartRegex, ['sectionStart', 'span']],
[sectionEndRegex, ['sectionEnd', 'span']],
[exports.tagRegex, ['tag', 'span']],
];
function getSpecialType(span) {
for (const [regex, result] of specialTypes) {
if (regex.test(span))
return result;
}
return [];
}
function* spans(chars) {
let i = 0;
while (i < chars.length) {
const char = chars[i];
console.assert(char.token[0] === 0, `token was not beginning of span`);
yield [chars.slice(i, i + char.token[1]), i];
i += char.token[1];
}
}
function markTree(parsed) {
const chars = parsed.chars;
const lineCount = 1 +
chars.reduce((prior, { char }) => {
return prior + (char === '\n' ? 1 : 0);
}, 0);
const lines = new Array(lineCount)
.fill(1)
.map(() => ({ tags: [] }));
let ln = 0;
const blocks = [];
const sections = [];
let sectionHold = null;
const result = [];
for (let i = 0; i < chars.length; i++) {
const char = chars[i];
if (char.token[0] === 0 && sectionHold != null) {
sections.unshift(sectionHold);
sectionHold = null;
}
result.push({
...char,
sections: [...sections],
});
if (char.token[0] !== 0) {
continue;
}
const span = getSpan(chars, i);
if (char.char === '\n') {
ln++;
lines[ln].tags.push(...blocks);
}
else if (char.classList.length === 1 && char.classList[0] === 'pl-c') {
if (nextLineRegex.test(span)) {
const name = span.match(nextLineRegex)?.[1];
lines[ln + 1].tags.push(name);
}
else if (thisLineRegex.test(span)) {
const name = span.match(thisLineRegex)?.[1];
lines[ln].tags.push(name);
}
else if (blockStartRegex.test(span)) {
blocks.push(span.match(blockStartRegex)?.[1]);
}
else if (blockEndRegex.test(span)) {
blocks.pop();
}
else if (sectionStartRegex.test(span)) {
sectionHold = span.match(sectionStartRegex)?.[1];
}
else if (sectionEndRegex.test(span)) {
sections.shift();
}
}
}
return {
...parsed,
lines,
chars: result,
};
}
/**
* @internal
* @param input - parsed or parsable variable
* @returns parsed code
*/
function ensureParsed(input) {
if (Array.isArray(input)) {
return parse(input[0], input[1]);
}
else if (typeof input === 'object' && 'code' in input) {
return parse(input.lang, input.code);
}
else {
return input;
}
}
exports.ensureParsed = ensureParsed;
let starryNight = null;
const starryNightPromise = (0, starry_night_1.createStarryNight)(starry_night_1.all);
starryNightPromise.then((sn) => (starryNight = sn));
function ready() {
return starryNightPromise;
}
exports.ready = ready;
function toString(code) {
const parsed = ensureParsed(code);
const result = [];
parsed.chars.forEach(({ char }) => result.push(char));
return result.join('');
}
exports.toString = toString;
function getSpan(tree, at) {

@@ -124,171 +198,54 @@ const [back, length] = tree[at].token;

}
function substitute(code, subs) {
exports.getSpan = getSpan;
/**
* Removes all code-fns tags.
*
* @param code - the parsed or parsable code to clean
*/
function clean(code) {
const parsed = ensureParsed(code);
const language = parsed.language;
const tree = parsed.chars;
const replacements = [];
let final = '';
tree.forEach((char, at) => {
if (char.token[0] !== 0)
return;
const span = getSpan(tree, at);
if (char.classList[0] === 'pl-c' && tagRegex.test(span)) {
const [, name] = span.match(tagRegex);
if (name in subs) {
const rep = subs[name];
final += rep;
if (rep !== '')
replacements.push([at, rep.length]);
}
else {
final += span;
}
const result = [];
let lineNumber = 0;
let finalNumber = 1;
let lineSkip = false;
const lines = [];
for (const [span] of spans(parsed.chars)) {
if (span.length === 1 && span[0].char === '\n')
lineNumber++;
if (lineSkip) {
console.assert(span.length === 1 && span[0].char === '\n', `expected a new line`);
lineSkip = false;
continue;
}
else {
final += span;
}
});
const reparsed = parse(language, final);
let [r, ri] = [0, 0];
let inReplacement = false;
return {
language,
chars: reparsed.chars.map((char, at) => {
if (inReplacement) {
ri++;
if (ri === replacements[r][1]) {
inReplacement = false;
r++;
}
}
else if (r < replacements.length) {
const [rat] = replacements[r];
if (rat === at) {
inReplacement = true;
}
}
return {
...char,
from: inReplacement ? 'new' : 'old',
};
}),
};
}
exports.substitute = substitute;
function transform(code, start, final) {
const tree = ensureParsed(code);
const before = substitute(tree, start);
const after = substitute(tree, final);
let [bat] = [0];
let [aat] = [0];
const chars = [];
while (bat < before.chars.length || aat < after.chars.length) {
const bchar = before.chars[bat] ?? null;
const achar = after.chars[aat] ?? null;
if (bchar?.from === 'old' && achar?.from === 'old') {
chars.push({
...achar,
from: 'keep',
else if (span.length === 1 && span[0].char === '\n') {
lines.push({
...parsed.lines[lineNumber],
number: finalNumber,
});
bat++;
aat++;
finalNumber++;
}
else if (bchar?.from === 'new') {
chars.push({
...bchar,
from: 'delete',
});
bat++;
console.assert(span[0].classList.length === 1, `multiple classes found`);
const specialTypes = getSpecialType(span.reduce((text, { char }) => text + char, ''));
if (span[0].classList[0] !== 'pl-c' || specialTypes.length === 0) {
result.push(...span);
}
else if (achar?.from === 'new') {
chars.push({
...achar,
from: 'create',
});
aat++;
else if (specialTypes.includes('line')) {
lineSkip = true;
}
}
return chars;
}
exports.transform = transform;
function transition(code, start, final) {
const tree = ensureParsed(code);
const chars = transform(tree, start, final);
const result = {
delete: [],
create: [],
retain: [],
lines.push({
...parsed.lines[lineNumber],
number: finalNumber,
});
if (lineSkip) {
lines.pop();
console.assert(result[result.length - 1].char === '\n', `expected a new line`);
result.pop();
}
return {
...parsed,
chars: result,
lines,
};
let [dln, dat] = [0, 0];
let [cln, cat] = [0, 0];
let lastColor = Symbol();
let lastFrom = Symbol();
chars.forEach((char) => {
const classList = char.classList;
console.assert(classList.length <= 1, `classList too long`);
const styles = classList.length === 1 ? rules.get(`.${classList[0]}`) : new Map();
console.assert((styles?.size ?? 0) <= 1, `more styles than just color`, styles);
const color = styles?.get('color');
if (char.char === '\n') {
if (char.from === 'keep' || char.from === 'create') {
cln++;
cat = 0;
}
if (char.from === 'keep' || char.from === 'delete') {
dln++;
dat = 0;
}
lastColor = Symbol();
lastFrom = Symbol();
}
else if (color === lastColor && char.from === lastFrom) {
if (char.from === 'delete') {
last(result.delete)[0] += char.char;
dat++;
}
else if (char.from === 'create') {
last(result.create)[0] += char.char;
cat++;
}
else if (char.from === 'keep') {
last(result.retain)[0] += char.char;
dat++;
cat++;
}
lastFrom = char.from;
lastColor = color;
}
else {
if (char.from === 'delete') {
result.delete.push([
char.char,
[dln, dat],
...(color ? [color] : []),
]);
dat++;
}
else if (char.from === 'create') {
result.create.push([
char.char,
[cln, cat],
...(color ? [color] : []),
]);
cat++;
}
else if (char.from === 'keep') {
result.retain.push([
char.char,
[dln, dat],
[cln, cat],
...(color ? [color] : []),
]);
dat++;
cat++;
}
lastFrom = char.from;
lastColor = color;
}
});
return result;
}
exports.transition = transition;
exports.clean = clean;
{
"name": "code-fns",
"version": "0.2.3",
"version": "0.3.0",
"description": "A library for visualizing code.",

@@ -21,3 +21,3 @@ "license": "MIT",

"scripts": {
"test": "vitest --ui",
"test": "vitest",
"dev": "vite",

@@ -28,3 +28,4 @@ "build": "tsc",

"prettier": "prettier --write .",
"lint": "eslint ./src/"
"lint": "eslint ./src/",
"coverage": "vitest run --coverage"
},

@@ -35,5 +36,7 @@ "devDependencies": {

"@typescript-eslint/parser": "^5.36.1",
"@vitest/coverage-c8": "^0.22.1",
"@vitest/ui": "^0.22.1",
"css": "^3.0.0",
"eslint": "^8.23.0",
"eslint-plugin-tsdoc": "^0.2.16",
"prettier": "^2.7.1",

@@ -40,0 +43,0 @@ "typescript": "^4.6.4",

@@ -14,3 +14,3 @@ # code-fns

Most code highlighters in JavaScript rely on HTML and CSS. When working outside
of a standard webpage, however, these formats become difficult to use. Code-fns
of a standard web page, however, these formats become difficult to use. Code-fns
is domain-agnostic, and will export tokens as plain objects to be converted to

@@ -37,25 +37,9 @@ whatever format you choose. Specifically, code-fns was built for use in the

```tsx
import { ready, tokenColors } from 'code-fns';
import { ready, color } from 'code-fns';
await ready();
const tokens = tokenColors(['tsx', '() => true']);
const code = color(['tsx', '() => true']);
```
You will receive an array of tokens, which are themselves a tuple of a string, a
location, and a color, when applicable. Colors are based on the github dark
theme, though we hope to add more themes in the future.
```tsx
// tokens
[
['() ', [0, 0]],
['=>', [0, 3], '#ff7b72'],
[' ', [0, 5]],
['true', [0, 6], '#79c0ff'],
];
```
Locations are always `[line, column]`.
### Transitioning code (for animations)

@@ -100,5 +84,4 @@

The `transform` object will contain three token arrays: "create", "delete", and
"retain". The `create` and `delete` arrays contains tuples with the token's
text, location, and then color, when available.
The `transform` object will contain an array of tokens, each of which with a
provinance property of either "create", "delete", or "retain".

@@ -112,35 +95,1 @@ ```tsx

```
The `transform` variable is then
```tsx
{
"create": [["false", [0, 0], "#79c0ff"]],
"delete": [["true", [0, 0], "#79c0ff"]],
"retain": [],
}
```
The `retain` array contains tuples with the token's text, old position, new
position, and color, when available.
```tsx
import { ready, transition, toString } from 'code-fns';
await ready();
const transform = transition(['tsx', '/*<t>*/true'], { t: '' }, { t: ' ' });
```
Here, the `transform` variable is
```tsx
{
"create": [[" ", [0, 0]]],
"delete": [],
"retain": [["true", [0, 0], [0, 4], "#79c0ff"]],
}
```
By interpolating between the old and new position, you may animate notes to
their new location.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc