Skip to main content
Home
This release is 1 version behind 1.0.9 — the latest version of @std/path. Jump to latest

Utilities for working with file system paths

This package works with Cloudflare Workers, Deno, Browsers
This package works with Cloudflare Workers
This package works with Deno
This package works with Browsers
JSR Score
100%
Published
6 months ago (1.0.8)
Package root>posix>extname.ts
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // This module is browser compatible. import { CHAR_DOT } from "../_common/constants.ts"; import { assertPath } from "../_common/assert_path.ts"; import { isPosixPathSeparator } from "./_util.ts"; /** * Return the extension of the `path` with leading period. * * @example Usage * ```ts * import { extname } from "@std/path/posix/extname"; * import { assertEquals } from "@std/assert"; * * assertEquals(extname("/home/user/Documents/file.ts"), ".ts"); * assertEquals(extname("/home/user/Documents/"), ""); * assertEquals(extname("/home/user/Documents/image.png"), ".png"); * ``` * * @example Working with URLs * * Note: This function doesn't automatically strip hash and query parts from * URLs. If your URL contains a hash or query, remove them before passing the * URL to the function. This can be done by passing the URL to `new URL(url)`, * and setting the `hash` and `search` properties to empty strings. * * ```ts * import { extname } from "@std/path/posix/extname"; * import { assertEquals } from "@std/assert"; * * assertEquals(extname("https://deno.land/std/path/mod.ts"), ".ts"); * assertEquals(extname("https://deno.land/std/path/mod.ts?a=b"), ".ts?a=b"); * assertEquals(extname("https://deno.land/std/path/mod.ts#header"), ".ts#header"); * ``` * * Note: If you are working with file URLs, * use the new version of `extname` from `@std/path/posix/unstable-extname`. * * @param path The path to get the extension from. * @returns The extension (ex. for `file.ts` returns `.ts`). */ export function extname(path: string): string { assertPath(path); let startDot = -1; let startPart = 0; let end = -1; let matchedSlash = true; // Track the state of characters (if any) we see before our first dot and // after any path separator we find let preDotState = 0; for (let i = path.length - 1; i >= 0; --i) { const code = path.charCodeAt(i); if (isPosixPathSeparator(code)) { // If we reached a path separator that was not part of a set of path // separators at the end of the string, stop now if (!matchedSlash) { startPart = i + 1; break; } continue; } if (end === -1) { // We saw the first non-path separator, mark this as the end of our // extension matchedSlash = false; end = i + 1; } if (code === CHAR_DOT) { // If this is our first dot, mark it as the start of our extension if (startDot === -1) startDot = i; else if (preDotState !== 1) preDotState = 1; } else if (startDot !== -1) { // We saw a non-dot and non-path separator before our dot, so we should // have a good chance at having a non-empty extension preDotState = -1; } } if ( startDot === -1 || end === -1 || // We saw a non-dot character immediately before the dot preDotState === 0 || // The (right-most) trimmed path component is exactly '..' (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) ) { return ""; } return path.slice(startDot, end); }