Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci-module.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: ci

on:
push:
branches:
- master
pull_request:
workflow_dispatch:
push:
branches:
- master
pull_request:
workflow_dispatch:

jobs:
test:
uses: hapijs/.github/.github/workflows/ci-module.yml@master
test:
uses: hapijs/.github/.github/workflows/ci-module.yml@min-node-22-hapi-21
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
**/node_modules
**/package-lock.json

coverage.*
coverage/

**/.DS_Store
**/._*
Expand Down
4 changes: 2 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

### `location(depth)`

Returns the filename and line number of the caller in the call stack where:
- `depth` - the distance from the `location()` function in the call stack. Defaults to `1` (caller).

- `depth` - the distance from the `location()` function in the call stack. Defaults to `0` (immediate caller); each increment walks one frame further up.

Returns an object with the `filename` and `line` number.
24 changes: 0 additions & 24 deletions lib/index.d.ts

This file was deleted.

21 changes: 0 additions & 21 deletions lib/index.js

This file was deleted.

8 changes: 8 additions & 0 deletions oxfmt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import DefaultOxfmtConfig from '@hapi/oxc-plugin/oxfmt';
import { defineConfig } from 'oxfmt';

import type { OxfmtConfig } from 'oxfmt';

export default defineConfig({
...DefaultOxfmtConfig,
}) as OxfmtConfig;
11 changes: 11 additions & 0 deletions oxlint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import HapiRecommended from '@hapi/oxc-plugin/oxlint';
import { defineConfig } from 'oxlint';

import type { OxlintConfig } from 'oxlint';

export default defineConfig({
extends: [HapiRecommended],
env: {
...HapiRecommended.env,
},
}) as OxlintConfig;
48 changes: 32 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
{
"name": "@hapi/pinpoint",
"description": "Return the filename and line number of the calling function",
"version": "2.0.1",
"repository": "git://github.com/hapijs/pinpoint",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib"
],
"description": "Return the filename and line number of the calling function",
"keywords": [
"utilities"
],
"dependencies": {},
"devDependencies": {
"@hapi/code": "^9.0.3",
"@hapi/lab": "^25.1.2",
"@types/node": "^14.18.36",
"typescript": "4.0.x"
"license": "BSD-3-Clause",
"repository": "git://github.com/hapijs/pinpoint",
"files": [
"src",
"API.md"
],
"type": "module",
"types": "src/index.d.ts",
"exports": {
".": {
"types": "./src/index.d.ts",
"default": "./src/index.js"
}
},
"scripts": {
"test": "lab -a @hapi/code -t 100 -L -Y",
"test-cov-html": "lab -a @hapi/code -t 100 -L -r html -o coverage.html"
"test": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --check",
"fmt:fix": "oxfmt",
"check": "npm run lint && npm run fmt && npm run typecheck && npm test"
},
"devDependencies": {
"@hapi/oxc-plugin": "^1.0.1",
"@vitest/coverage-v8": "^4.1.9",
"oxfmt": "^0.57.0",
"oxlint": "^1.72.0",
"typescript": "^6.0.3",
"vitest": "^4.1.9"
},
"license": "BSD-3-Clause"
"engines": {
"node": ">=22"
}
}
17 changes: 17 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Returns the filename and line number of the caller in the call stack
*
* @param depth - The distance from the location function in the call stack. Defaults to 1 (caller).
* @returns An object with the filename and line number.
*/
export function location(depth?: number): location.Location;

declare namespace location {
interface Location {
/** The fully qualified filename. */
readonly filename: string;

/** The file line number. */
readonly line: number;
}
}
21 changes: 21 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { fileURLToPath } from 'url';

export function location(depth = 0) {
const orig = Error.prepareStackTrace;
Error.prepareStackTrace = (ignore, stack) => stack;

const capture = {};
Error.captureStackTrace(capture);
const line = capture.stack[depth + 1];

Error.prepareStackTrace = orig;

// Under ESM, CallSite.getFileName() returns a file:// URL; normalize back to a
// filesystem path so the returned filename matches the CJS-era contract.
const filename = line.getFileName();

return {
filename: filename && filename.startsWith('file://') ? fileURLToPath(filename) : filename,
line: line.getLineNumber(),
};
}
41 changes: 17 additions & 24 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
'use strict';

const Path = require('path');

const Code = require('@hapi/code');
const Lab = require('@hapi/lab');
const Pinpoint = require('..');


const internals = {};


const { describe, it } = exports.lab = Lab.script();
const expect = Code.expect;
import { describe, expect, it } from 'vitest';

import * as Pinpoint from '../src/index.js';

describe('Pinpoint', () => {

describe('location()', () => {

it('returns current location', () => {

expect(Pinpoint.location()).to.equal({
filename: Path.join(__dirname, 'index.js'),
line: 23
});
it('returns the filename and line of the calling frame', () => {
const first = Pinpoint.location();
const second = Pinpoint.location(); // exactly one source line below `first`

expect(first.filename).toContain('index.js');
expect(first.filename.startsWith('file://')).toBe(false); // normalized to a path, not a URL
expect(typeof first.line).toBe('number');
expect(second.line - first.line).toBe(1);
});

it('returns location or upper caller', () => {
it('walks up the stack with depth', () => {
const inner = () => Pinpoint.location(1); // depth 1 → inner's caller

const mark = Pinpoint.location(); // depth 0 → this line
const up = inner(); // inner() is one source line below `mark`

expect(Pinpoint.location(1).filename).to.contain(Path.join('@hapi', 'lab'));
expect(up.filename).toContain('index.js');
expect(up.line - mark.line).toBe(1);
});
});
});
18 changes: 0 additions & 18 deletions test/index.ts

This file was deleted.

26 changes: 26 additions & 0 deletions test/typings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expectTypeOf, it } from 'vitest';

import * as Pinpoint from '../src/index.js';

describe('typings', () => {
describe('location', () => {
it('return type + signature compile', () => {
expectTypeOf(Pinpoint.location).toBeFunction();
expectTypeOf(Pinpoint.location()).toEqualTypeOf<Pinpoint.location.Location>();
expectTypeOf(Pinpoint.location().filename).toBeString();
expectTypeOf(Pinpoint.location().line).toBeNumber();
expectTypeOf(Pinpoint.location(1)).toEqualTypeOf<Pinpoint.location.Location>();
});

it('invalid calls rejected', () => {
// The @ts-expect-error directives are the assertion; the calls still run, so
// wrap them — a non-numeric depth throws at runtime (the type errors are what we test).
try {
// @ts-expect-error depth takes at most one argument
Pinpoint.location(1, 2);
// @ts-expect-error depth must be a number
Pinpoint.location('1');
} catch {}
});
});
});
25 changes: 25 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": true,
"isolatedDeclarations": true,
"isolatedModules": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "ESNext"
}
}
23 changes: 23 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Oxc from '@hapi/oxc-plugin/vitest';
import { defineConfig } from 'vitest/config';

import type { ViteUserConfig } from 'vitest/config';

export default defineConfig({
plugins: [Oxc()],
test: {
environment: 'node',
include: ['test/**/*.{js,ts}'],
typecheck: {
enabled: true,
include: ['test/**/*.{js,ts}'],
},
coverage: {
provider: 'v8',
include: ['src/**'],
thresholds: {
100: true,
},
},
},
}) as ViteUserConfig;
Loading